3

给定以下示例:

class BaseClass
{
  BaseClass()
  {
  };

  virtual ~BaseClass()
  {
    this->Cleanup();
  };

  virtual void Cleanup()
  {
    // Do cleanup here.
  };
};

class Level1DerivedClass : public BaseClass
{
  Level1DerivedClass()
  {
  };

  virtual ~Level1DerivedClass()
  {
  };

  virtual void Cleanup()
  {
    // Call my base cleanup.
    BaseClass::Cleanup();

    // Do additional cleanup here.
  };
};

class Level2DerivedClass : public Level1DerivedClass
{
  Level2DerivedClass()
  {
  };

  ~Level2DerivedClass()
  {
  };

  void Cleanup()
  {
    // Call my base cleanup.
    Level1DerivedClass::Cleanup();

    // Do additional cleanup here.
  };  
};


main()
{
  Level2DerivedClass * derived2 = new Level2DerivedClass();
  delete derived2;
  return 0;
}

当我删除派生类引用时,我希望流程如下:

  1. 执行Level2DerivedClass析构函数。
  2. 因为Level1DerivedClass析构函数是虚拟的,所以它会被执行。
  3. 因为BaseClass析构函数是虚拟的,所以它会被执行。
  4. 因为BaseClass::CleanupLevel1DerivedClass::Cleanup都是虚拟的,所以从BaseClass析构函数中的BaseClass 'this' 指针的调用将执行最派生类 - Level2DerivedClass::Cleanup的实现。
  5. Level2DerivedClass::Cleanup调用其父级的Cleanup实现。
  6. Level1DerivedClass::Cleanup调用其父级的Cleanup实现。

正在发生的事情是它以我期望的方式为每个继承级别(1-3)调用析构函数。但是当从BaseClass析构函数调用this->Cleanup()时,它只执行自己的实现。我不明白为什么会发生这种情况,因为通常当您实例化派生类指针,将其转换为基类指针并从基类指针调用虚拟方法(在本例中为“this”)时,它仍然运行派生类实现(“虚拟”的全部意义,是吗?)。在我的示例中,Level2DerivedClass::CleanupLevel1DerivedClass::Cleanup永远不会被调用。

我以这种方式设置它的原因是我希望能够调用我的清理代码而不必破坏我的对象,这就是我从实际的析构函数体中抽象它的原因。

如果您对更合适的方法有建议,我会全力以赴。但我也想解释为什么我的设置不起作用 - 我误解了什么?

提前感谢您的宝贵时间。

4

2 回答 2

7

经验法则是:永远不要在构造或破坏期间调用虚函数

他们的行为不像你想象的那样;随着每个析构函数完成,动态类型this被有效地修改。来自 C++ 标准中的 [class.cdtor]:

当从构造函数(包括非静态数据成员的 mem-initializer 或大括号或等式初始化器)或从析构函数直接或间接调用虚函数时,调用适用的对象是对象在构造或析构中,调用的函数是在构造函数或析构函数自己的类或其基类之一中定义的函数,但不是在从构造函数或析构函数的类派生的类中覆盖它的函数,或在其中一个类中覆盖它的函数最派生对象的其他基类。

于 2012-05-30T17:10:46.517 回答
2

正确的做事方式是:在析构函数中清理自己,并且只清理自己。不要在你的孩子或你的父母之后打扫卫生。

如果你想清理不是从析构函数中的东西,你做错了。在 C++ 中,我们有一个叫做 RAII 的小东西,资源获取就是初始化。但也有它的双重属性,它似乎没有一个正式的名称,但这里有一些可行的方法:RDID,资源处置就是破坏。

当然,您不必遵守 RAII/RDID 理念,但这不是 C++ 方式。

于 2012-05-30T17:32:56.273 回答