c++ - CRTP 在基本构造函数中调用子虚拟覆盖
问题描述
以下示例产生此运行时错误:
pure virtual method called
terminate called without an active exception
例如
template<class DERIVED>
struct Base {
int val;
Base(int i) : val(static_cast<DERIVED*>(this)->init_val(i)) {}
virtual int init_val(int i) = 0;
};
struct Derived : public Base<Derived> {
Derived(int i): Base<Derived>(i) {}
int init_val(int i) override {return i;}
};
int main(){
Derived d{100};
cout << d.val;
}
另一方面,下一个示例有效;按预期打印 100:
template<class DERIVED>
struct Base {
int foo(int i){return static_cast<DERIVED*>(this)->bar(i);}
virtual int bar(int i) = 0;
};
struct Derived : public Base<Derived> {
int bar(int i) override {return i;}
};
int main(){
Derived d;
cout << d.foo(100);
}
也许最有趣的是,如果您不将 init_val() 声明为虚拟/覆盖函数,它就会起作用。有人建议这是重复的。我对可能重复的问题的理解是,由于子级尚不存在,因此无法在基本构造函数中调用虚函数。鉴于代码在 init_val 不是虚拟时有效,因此建议的重复问题不适用于此处。
Clang 和 g++ 使用 c++17 产生相同的行为。
为什么在方法中调用基类构造函数中的类型转换虚拟函数会失败?
更新: immibis 的建议奏效了。即将构造函数中对虚拟基的调用更改为:
static_cast<DERIVED*>(this)->DERIVED::init_val(i)
这“安全”吗?为什么它有效?
解决方案
这“安全”吗?
不,您正在调用一个尚不存在的对象的成员函数。在调用基类构造函数的时候,派生类还没有出现。因此,您不能在其上调用成员函数。
它只是偶然“起作用”;未定义的行为是未定义的。