关于CRTP(奇异递归模板模式)的合法性及类型转换疑问
namespace static_poly { template<typename T> class Foo { public: std::string Name() { return static_cast<T*>(this)->GetName(); } }; class AA : public Foo<AA> { public: std::string GetName() { name_ = "AA"; return name_; } std::string name_; }; void Test() { Foo<AA> foo_aa; foo_aa.Name(); // legal foo_aa.name_ = "hello"; // illagel } } // static_poly
疑问解答
疑问1:我理解
foo_aa.name_非法,因为foo_aa是Foo<AA>类对象,而非AA类对象,不具备name_成员
你的理解完全正确。Foo<AA>是基类,AA是派生类,基类对象本身不包含派生类的成员变量name_,直接访问必然编译报错。疑问2:但我不理解
foo_aa.Name()为何合法,因为未定义任何AA类对象,内存中不存在name_变量,Name函数如何访问name_?foo_aa.Name()能通过编译,但运行时会触发未定义行为。编译阶段编译器只做语法和类型检查:Foo<AA>::Name里的static_cast<T*>(this)在语法上是允许的(因为AA确实继承自Foo<AA>),编译器不会验证this指向的对象实际是不是AA类型。但运行时,foo_aa只是Foo<AA>对象,没有AA的内存部分,调用GetName()访问name_会触及非法内存,大概率崩溃或出现乱码。疑问3:具体来说,我知道
Name函数中进行了static_cast<T*>(this)转换,但不理解该转换为何有效——仅存在一个Foo<AA>基类对象,为何能转换为派生类AA?
这个转换在编译阶段是合法的,因为编译器知晓AA是Foo<AA>的派生类,static_cast允许这种基类指针到派生类指针的转换。但运行时这个转换是错误的,因为this指向的对象根本不是AA实例,没有派生类的内存布局。CRTP的正确用法里,Foo<T>的对象必须作为T的基类子对象存在,而非单独实例化Foo<T>。疑问4:根据基类到派生类转换的规则,此类转换行为是未定义的,为何CRTP中这样使用是可行的?
你写的是CRTP的错误用法!CRTP的核心是派生类继承自以自身为模板参数的基类,且只能通过派生类对象来使用基类功能,比如正确用法应该是创建AA对象:AA aa; aa.Name(); // 完全合法,因为aa是AA实例,this指向的基类子对象属于AA,static_cast<T*>(this)能正确指向完整的AA对象单独实例化基类
Foo<AA>本身就违背了CRTP的设计意图,这种情况下的行为本来就是未定义的,不是CRTP本身不可靠,是用法错了。
内容的提问来源于stack exchange,提问作者maxentroy

