从析构函数调用虚函数

时间:2012-08-23 13:38:53

标签: c++ virtual

这样安全吗?

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

5 个答案:

答案 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();
   }
}