这样安全吗?
class Derived: public PublicBase, private PrivateBase
{
...
~Derived()
{
FunctionCall();
}
virtual void FunctionCall()
{
PrivateBase::FunctionCall();
}
}
class PublicBase
{
virtual ~PublicBase(){};
virtual void FunctionCall() = 0;
}
class PrivateBase
{
virtual ~PrivateBase(){};
virtual void FunctionCall()
{
....
}
}
PublicBase* ptrBase = new Derived();
delete ptrBase;
此代码在错误的地址中使用IP 有时 。
对于每个人来说,在构造函数上调用虚函数并不是一个好主意。
从像http://www.artima.com/cppsource/nevercall.html这样的文章中我理解析构函数也是一个不太适合调用虚函数的地方。
我的问题是“这是真的吗?”我已经测试过VS2010和VS2005并且调用了PrivateBase :: FunctionCall。未定义的行为?
答案 0 :(得分:63)
我将反对这里的流程......但首先,我必须假设你的PublicBase
析构函数是虚拟的,否则永远不会调用Derived
析构函数。
从构造函数/析构函数中调用虚函数通常不是一个好主意
原因是动态调度在这两个操作中很奇怪。构造期间对象的实际类型会更改,并且在销毁期间会再次更改。当一个析构函数被执行时,该对象就是那种类型,而不是从它派生的类型。动态调度始终有效,但虚拟函数的最终覆盖将根据您所在的层次结构而发生变化。
也就是说,你永远不应该期望在构造函数/析构函数中对虚函数的调用是在从正在执行的构造函数/析构函数的类型派生的任何类型中执行的。
但是
在您的特定情况下, final 覆盖(至少对于层次结构的这一部分)高于您的级别。此外,您根本没有使用动态调度。呼叫PrivateBase::FunctionCall();
是静态解析的,实际上相当于对任何非虚函数的调用。函数虚拟的事实不会影响此调用。
所以是这样做很好,尽管你会被迫在代码审查中解释这一点,因为大多数人都会学习规则的咒语,而不是原因。
答案 1 :(得分:19)
这样安全吗?
是。从构造函数或析构函数调用虚函数调度该函数,就好像该对象的动态类型是当前正在构造或销毁的那样。在这种情况下,它是从Derived
的析构函数调用的,因此它被调度到Derived::FunctionCall
(在您的情况下,非虚拟地调用PrivateBase::FunctionCall
)。所有这一切都很明确。
从构造函数或析构函数调用虚函数是“不是一个好主意”有三个原因:
答案 2 :(得分:2)
通常,调用虚函数不是一个好主意,除非它可能被调度到的类的对象(即,最派生类的“完整”对象)是完全构造的。而事实并非如此
答案 3 :(得分:1)
根据斯科特的说法,这是一个非常糟糕的主意:link
这是我编译和运行以帮助自己更好地理解销毁过程的内容,您可能也会发现它有用
#include <iostream>
using namespace std;
class A {
public:
virtual void method() {
cout << "A::method" << endl;
}
void otherMethod() {
method();
}
virtual ~A() {
cout << "A::destructor" << endl;
otherMethod();
}
};
class B : public A {
public:
virtual void method() {
cout << "B::method" << endl;
}
virtual ~B() {
cout << "B::destructor" << endl;
}
};
int main() {
A* a = new B();
a->method();
delete a;
}
答案 4 :(得分:0)
这样安全吗?
是和否
是的,因为您的示例按原样定义良好并且可以正常工作。所有这些都可以通过其他答案很好地解释。另外,此代码是完全安全的,因为它不会像编写代码那样进行编译:基类中的私有dtor等。
用示例的方式进行操作并不安全,是因为此代码假定其他人不会覆盖您的FunctionCall
类中的Derived
,并且在对象破坏时调用的重写。最有可能的编译器会对此抱怨。您可以通过将FunctionCall
标记为final来改进代码:
class Derived : public PublicBase, private PrivateBase
{
...
virtual void FunctionCall() final;
}
或您的Derived
类为final:
class Derived final : public PublicBase, private PrivateBase
{
...
virtual void FunctionCall();
}
如果您在较旧的编译器上进行编译,或者由于任何其他原因而无法使用c++11,那么您至少可以在此处更明确地将代码准确地写在运行时,无论FunctionCall
是否为被您的Derived
类的任何后代所覆盖:
class Derived : public PublicBase, private PrivateBase
{
...
~Derived()
{
Derived::FunctionCall(); // be explicit which FunctionCall will be called
}
virtual void FunctionCall()
{
PrivateBase::FunctionCall();
}
}