CRTP 和向下转型

get*_*ubl 1 c++ crtp

跟进类似的问题以前的帖子关于向下转换和类型安全,不知下面的示例创建未定义的行为。

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

Que*_*tin 5

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

类型为“指向cv1 的 指针”的纯右值B,其中B是类类型,可以转换为类型为“指向cv2 的 指针”的纯右值D,其中D是从 派生的完整类B,如果cv2与 cv 限定相同或更高cv-资格比,cv1。[...]如果“指向cv1 的 指针B”类型的纯右值指向B实际上是类型对象的子对象的 aD,则结果指针指向类型为封闭对象的对象D否则,行为是 undefined

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

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

protected:
    Base() = default;
};
Run Code Online (Sandbox Code Playgroud)

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

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

private:
    friend Derived;
    Base() = default;
};
Run Code Online (Sandbox Code Playgroud)

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

  • 大多数情况下,使用 CRTP 时,构造函数应该受到“保护”,除非它是抽象的,因此只能构造派生类型。根据我的经验,很少可以直接构造 CRTP 基本类型的实例。 (3认同)