CRTP 和向下转型

CRTP and downcasting

跟进 中关于向下转换和类型安全的类似问题,我想知道以下示例是否会产生未定义的行为。

已创建 Base class 实例。没有发生动态绑定。
但是,在 Base::interface 函数中,Base class 的实例被转换为 Derived class 的实例。 这样安全吗?如果是,为什么? 请在下面找到这段代码。

#include <iostream>
template <typename Derived>
struct Base{
  void interface(){
    static_cast<Derived*>(this)->implementation();
  }
};

struct Derived1: Base<Derived1>{
  void implementation(){
    std::cout << "Implementation Derived1" << std::endl;
  }
};
        
int main(){
  
  std::cout << std::endl;
  Base<Derived1> d1;
  d1.interface();
  std::cout << std::endl;
}

没有 Derived 这个新的 Derived * 可以指向,所以它绝对不安全:演员表本身有未定义的行为,如 [expr.static.cast] 中所述§ 11(强调我的):

A prvalue of type “pointer to cv1 B”, where B is a class type, can be converted to a prvalue of type “pointer to cv2 D”, where D is a complete class derived from B, if cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] If the prvalue of type “pointer to cv1 B” points to a B that is actually a subobject of an object of type D, the resulting pointer points to the enclosing object of type D. Otherwise, the behavior is undefined.

您可以通过限制对 Base 的构造函数的访问来减轻这种风险:

template <typename Derived>
struct Base{
    // Same as before...

protected:
    Base() = default;
};

这样更好,但如果有人不小心定义了 struct Derived2 : Base<AnotherDerived> { };,仍然会出现同样的问题。可以通过特定的 friend 声明来防止这种情况发生,但缺点是会完全访问 Base 的私有成员:

template <typename Derived>
struct Base{
    // Same as before...

private:
    friend Derived;
    Base() = default;
};

请注意,这仍然允许 Derived 在其成员函数中构造裸 Base<Derived> 对象,但这是我通常停止打地鼠的地方。