44

这安全吗?

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

4

5 回答 5

76

我将在这里逆流而上……但首先,我必须假设您的PublicBase析构函数是虚拟的,否则Derived将永远不会调用析构函数。

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

原因是在这两个操作期间动态调度很奇怪。对象的实际类型在构造过程中发生变化,并且在销毁过程中再次发生变化。当一个析构函数被执行时,对象就是那个类型,而不是从它派生的类型。动态调度始终有效,但虚函数的最终覆盖将根据您在层次结构中的位置而改变。

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

在您的特定情况下,最终覆盖者(至少对于层次结构的这一部分)高于您的级别。此外,您根本没有使用动态调度。该调用PrivateBase::FunctionCall();是静态解析的,并且实际上等效于对任何非虚拟函数的调用。该函数是否为虚拟函数这一事实不会影响此调用。

所以的,按照你的方式做是很好的,尽管你将被迫在代码审查中解释这一点,因为大多数人都了解规则的口头禅而不是它的原因。

于 2012-08-23T13:53:44.007 回答
26

这安全吗?

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

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

  • 如果您从基类调用它并且(错误地)期望它被分派到派生类中的覆盖,它将导致意外行为;
  • 如果它是纯虚拟的,它将导致未定义的行为;
  • 你将不得不继续向那些认为这样做总是错误的人解释你的决定。
于 2012-08-23T14:42:39.807 回答
3

一般来说,调用虚函数不是一个好主意,除非它可能被分派到的类的对象(即最派生类的“完整”对象)是完全构造的。事实并非如此

  • 直到所有构造函数完成执行
  • 在任何析构函数完成执行后
于 2012-08-23T13:42:53.620 回答
1

根据斯科特的说法,这是一个非常糟糕的主意:链接

这是我编译并运行的内容,以帮助自己更好地了解销毁过程,您可能还会发现它有帮助

#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;

}
于 2012-08-23T13:44:20.910 回答
0

Is this safe ?

Yes and No.

Yes, because your example as-is is well defined and will work. All of that is well explained by other answers. Also, this code is totally safe because it won't compiler the way it's written: private dtors in base classes etc.

The reason it's not safe to do the way your example does is because this code assumes that nobody else will override FunctionCall from your Derived class and won't expect that override to be called on object destruction. Most likely compilers will complain about this. You can improve your code by marking your FunctionCall as final:

class Derived : public PublicBase, private PrivateBase
{
 ... 
   virtual void FunctionCall() final;
}

or your Derived class as final:

class Derived final : public PublicBase, private PrivateBase
{
 ... 
   virtual void FunctionCall();
}

If you compile on older compilers or cannot use c++11 for any other reason, then you may at least be more explicit here and write in your code exactly what will happen at runtime regardless if FunctionCall is overridden by any descendant of your Derived class:

class Derived : public PublicBase, private PrivateBase
{
 ... 
   ~Derived()
   {
      Derived::FunctionCall(); // be explicit which FunctionCall will be called
   }

   virtual void FunctionCall()
   {
      PrivateBase::FunctionCall();
   }
}
于 2019-10-02T04:48:31.340 回答