首页 > 解决方案 > 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)

这“安全”吗?为什么它有效?

标签: c++templatesinheritancepolymorphismcrtp

解决方案


这“安全”吗?

不,您正在调用一个尚不存在的对象的成员函数。在调用基类构造函数的时候,派生类还没有出现。因此,您不能在其上调用成员函数。

它只是偶然“起作用”;未定义的行为是未定义的。


推荐阅读