在抽象构造函数/析构函数中调用纯虚拟函数安全吗

Is it safe to call a pure virtual function in an abstract constructor/destructor, IF it has a body?

本文关键字:虚拟 函数 安全 调用 抽象 构造函数 析构函数      更新时间:2023-10-16

如果没有标有BODY的线,我知道这是不安全的。但有了它,这安全吗?

struct A
{
    virtual ~A() { f(); }
    virtual void f() = 0;
};
void A::f() {} // BODY
struct B : A
{
    void f() {}
};
int main()
{
    delete new B;
}

工作示例:http://ideone.com/9bRZ3i

不,那不安全。当A构造函数(或析构函数)正在执行时,对象的类型是A,而不是(不再是)B对象。对f()的调用将尝试调度到(仍然)纯虚拟函数,并导致未定义的行为。大多数实现都会捕捉到这一点,并使用错误消息终止应用程序,该消息指示调用了纯虚拟函数。


编辑后:

纯虚拟函数有一个定义,这意味着在不经过虚拟调度的情况下调用它是合法的。使用动态调度调用纯虚拟函数仍然是非法的。但是您可以将构造函数重写为:

A::~A() { A::f(); }  // qualification disables dynamic dispatch

如果没有动态调度,代码将变为有效。

如果你想绕过虚拟调度并调用你定义的函数体,你必须限定函数名:

virtual ~A() { A::f(); } // OK.

否则,调用将启动虚拟调度,但仅对基类进行,因为派生类型的对象在其基类之前已经被销毁。

C++11§12.7/4直接解决了您的问题:

成员函数,包括虚拟函数(10.3),可以在构造或销毁(12.6.2)期间调用。当从构造函数或析构函数直接或间接调用虚拟函数时,包括在类的非静态数据成员的构造或销毁期间,调用所应用的对象是正在构造或销毁的对象(调用它x),调用的函数是构造函数或析构函数类中的最后一个重写器,而不是在更派生的类中重写它如果虚拟函数调用使用显式类成员访问(5.2.5),并且对象表达式引用x的完整对象或该对象的基类子对象之一,而不是x或其基类子对象之一时,则行为未定义。

然而,§10.4/6禁止使用纯虚拟函数进行此操作:

成员函数可以从抽象类的构造函数(或析构函数)中调用;从这样的构造函数(或析构函数)直接或间接地为正在创建(或销毁)的对象对纯虚拟函数进行虚拟调用(10.3)的效果是未定义的。

所以,它是UB。

"纯虚拟"的作用是从虚拟查找中隐藏函数定义。您永远不会从动态调度函数调用中获得纯虚拟函数的定义,除非可能是未定义行为的影响。