从析构函数调用虚拟函数

问题描述 投票:36回答:5

这安全吗?

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。是未定义的行为吗?

c++ virtual
5个回答
66
投票

我将在这里违背流程...但是首先,我必须假定您的PublicBase析构函数是虚拟的,否则将永远不会调用Derived析构函数。

从构造函数/析构函数调用虚拟函数通常不是一个好主意。]

原因是在这两个操作期间动态调度很奇怪。在构造过程中对象changes

的实际类型,在销毁过程中再次为对象changes。当执行析构函数时,对象正是该类型,而不是从其派生的类型。动态调度始终有效,但是虚拟函数的final Overrover会根据您在层次结构中的位置而变化。

也就是说,永远不要期望以任何从执行的构造函数/析构函数的类型派生的类型执行对构造函数/析构函数中的虚函数的调用。

但是

在您的特定情况下,final

替代程序(至少对于层次结构的此部分而言)是您的级别。而且,您完全是不使用动态分配。调用PrivateBase::FunctionCall();是静态解析的,实际上等效于对任何非虚拟函数的调用。函数是否为virtual的事实并不影响此调用。所以

可以按照您的意愿做,尽管大多数人都会学习规则的原则而不是原因,但是您将不得不在代码审查中解释这一点。
这安全吗?

是。从构造函数或析构函数调用虚拟函数会分派该函数,就像该对象的动态类型是当前正在构造或销毁的那样。在这种情况下,它是从Derived的析构函数调用的,因此它被分派到Derived::FunctionCall(在您的情况下,它是非虚拟地调用PrivateBase::FunctionCall的)。所有这些都定义明确。

出于以下三个原因,从构造函数或析构函数调用虚拟函数不是一个好主意:

    如果您从基类中调用它,并且(错误地)期望将其分派到派生类中的重写,它将导致意外的行为;
  • 如果它是纯虚拟的,它将导致不确定的行为;
  • 您将不得不向那些认为总是错的人解释您的决定。
  • 通常,除非要完全将其分配给该类的对象(即,派生最多的类的“完整”对象),否则调用虚拟函数不是一个好主意。事实并非如此

      直到所有构造函数完成执行
  • 在任何析构函数完成执行之后
  • 根据斯科特,这是一个非常糟糕的主意: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; }

    这安全吗?

    是和否

    是的,因为您的原样示例定义明确,可以正常工作。所有这些都可以通过其他答案很好地解释。另外,此代码是完全安全的,因为它不会以编写的方式进行编译:基类中的私有dtor等。

    这样做的原因是不安全的,因为此代码假定其他人都不会覆盖您的FunctionCall类中的Derived

    and

    不会期望在销毁对象时调用该覆盖。 。最有可能的编译器会对此抱怨。您可以通过将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(); } }


  • 20
    投票
    这安全吗?

    2
    投票
    通常,除非要完全将其分配给该类的对象(即,派生最多的类的“完整”对象),否则调用虚拟函数不是一个好主意。事实并非如此

    1
    投票
    根据斯科特,这是一个非常糟糕的主意:link

    0
    投票
    这安全吗?
    © www.soinside.com 2019 - 2024. All rights reserved.