C++ 在抽象构造函数/析构函数中调用纯虚函数(如果它有一个主体)安全吗?

C++ 在抽象构造函数/析构函数中调用纯虚函数(如果它有一个主体)安全吗?,c++,C++,如果没有标有BODY的线,我知道这是不安全的。但有了它,这安全吗 struct A { virtual ~A() { f(); } virtual void f() = 0; }; void A::f() {} // BODY struct B : A { void f() {} }; int main() { delete new B; } 工作示例: 不,那不安全。当A构造函数(或析构函数)正在执行时,对象的类型是A,而不是(不再是)AB对象。调用f

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

struct A
{
    virtual ~A() { f(); }

    virtual void f() = 0;
};

void A::f() {} // BODY

struct B : A
{
    void f() {}
};

int main()
{
    delete new B;
}
工作示例:
不,那不安全。当
A
构造函数(或析构函数)正在执行时,对象的类型是
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


“纯虚拟”的效果是对虚拟查找隐藏函数定义。您永远无法通过动态分派函数调用获得纯虚拟函数的定义,除非可能是由于未定义行为的影响。

调用纯虚拟函数的是析构函数,而不是构造函数。我添加了一个工作示例。你能解释一下为什么行为没有定义吗?@NeilKirk:没有定义是因为标准是这么说的。C++11,10.4/6,“对于从此类构造函数(或析构函数)创建(或销毁)的对象,直接或间接对纯虚拟函数进行虚拟调用的效果尚未定义。”@MikeSeymour有不允许的原因吗?@MikeSeymour:我认为这被12.7p4
排除了。调用的函数是构造函数或析构函数类中的最终重写器,而不是一个在更派生的类中重写它的函数。
没有虚拟调用,因此没有UB。这是什么意思“除了对构造函数/析构函数中虚拟查找行为不同的关注之外"? 这有点像房间里的大象,不是吗?@potatosatter我知道当你调用a中的虚函数时,它调用a的虚函数而不是B,这不是一些人所期望的。我不想失去对那件事的关注。我将删除该语句,因为它令人困惑。我甚至不知道可以为纯虚拟成员方法提供函数体。这有什么意义?什么时候叫它?只有从ctor还是dtor?@Walter有两种用途。第一个是当提供纯虚拟析构函数时,必须提供一个实体。第二个是为函数提供一个“默认”实现,该实现仍然必须被重写。如果派生类的函数愿意,它可以选择调用纯虚函数。我不相信在构造函数/析构函数中不加限定地调用函数实际上是这样做的。我们拭目以待@NeilKirk:在构造函数中,编译器可以,也可能会,将其优化为非虚拟调用,但不需要这样做。如果C++被间接调用(即构造函数调用其他函数,调用虚拟函数),那么它就更可能被称为“C++”。所以,这是我的荣幸:)。