CRTP 和向下转型

问题描述

previous post 中关于向下转换和类型安全的类似问题之后,我想知道以下示例是否会创建未定义的行为。

一个 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;
}

解决方法

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

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

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

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 对象,但这就是我通常停止敲打鼹鼠的地方。