如果没有标有 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
如果没有标有 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
构造函数(或析构函数)正在执行时,对象是 type A
,还不是(不再是)B
对象。调用f()
将尝试分派到(仍然)纯虚函数并导致未定义的行为。大多数实现都会捕捉到这一点并终止应用程序,并显示一条错误消息,表明调用了纯虚函数。
编辑后:
有一个纯虚函数定义的事实意味着在不经过 virtual dispatch的情况下调用它是合法的。使用动态调度调用纯虚函数仍然是非法的。但是您可以将构造函数重写为:
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。
“纯虚拟”的作用是对虚拟查找隐藏函数定义。您永远不会从动态调度函数调用中获得纯虚函数的定义,除非可能是未定义行为的影响。