10

从 C++ 常见问题解答:

[11.4] 我可以为我的班级重载析构函数吗?不。

我意识到这意味着您不能更改返回类型、参数的类型或参数的数量。我可能会对单词的语法感到困惑,但是是否可以覆盖父级的析构函数?

class Child : public Parent {
public:
    virtual Parent::~Parent() {
        // New definition
    }
};

就此而言,它是递归的吗?

class Grandchild : public Child {
public:
    Child::Parent::~Parent() {
        // An even newer definition
    }
};

我已经阅读了这篇文章和一篇相关文章,这让我觉得因为析构函数不是继承的,它们不能被覆盖,但我从未见过它明确说明。

编辑:我改变了这个以反映我想覆盖父析构函数的事实,注意孩子和孙子覆盖〜Parent()。

我这样做的主要原因是为了维护 Parent 的接口,同时改变它的销毁方式(子类的全部原因)。我将有其他东西来管理所有 Parent 的创建,并在我选择的稍后时间显式调用它们的析构函数。

4

4 回答 4

9

我可能对单词的语法感到困惑

不,你绝对不是——这是两件截然不同的事情。

但是可以覆盖析构函数吗?

是的,事实上在很多情况下你必须这样做。为了使它适用于多态对象,您需要将基类析构函数声明为virtual,但是:

Parent const& p = Child();

p.~Child()将在范围结束时正确调用,因为Parent::~Parent它是虚拟的。

于 2013-06-13T17:40:42.047 回答
8

是的,可以覆盖类的析构函数。事实上,当你定义一个使用多态的类层次结构时,你必须在基类中声明一个虚析构函数。

析构函数重载的工作方式与普通成员函数重载的工作方式完全相同,因为当您delete通过指向基类的指针来销毁对象时,派生类的析构函数会被正确调用。这就是为什么在多态层次结构的基类中必须有一个虚拟析构函数。

virtual但是,虚拟析构函数和虚拟成员方法之间存在差异,这与析构函数的性质无关。也就是说,当执行这样的代码时:

class A
{
public:  
  virtual void Foo() {}
  virtual ~A() {};
};

class B : public A
{
public:
  void Foo() {};
  ~B() {}
};

int main()
{
  A* a = new B;
  a->Foo();  // B::Foo() is called
  delete a;  // B is destroyed via B::~B()
}

...当您调用时,将调用in中a->Foo()的方法。由于没有显式调用,因此不会调用。Foo()BB::Foo()A::Foo()A::Foo()

但是,当通过 销毁对象时delete a;,首先B::~B()调用析构函数,然后在完成之后但在控制权返回程序之前,还会调用基类析A::~A()构函数。

当然,当您考虑它时,这是显而易见的,而且这与析构函数的性质无关virtual,但它的行为确实与正常的virtual方法调用不同,所以我想我会指出它。

强制性标准报价:

[C++03] 12.4/6:析构函数

在执行析构函数的主体并销毁主体内分配的任何自动对象后,类 X 的析构函数调用 X 的直接成员的析构函数、X 的直接基类的析构函数,如果 X 是最派生类的类型( 12.6.2),它的析构函数调用 X 的虚拟基类的析构函数。所有的析构函数都被调用,就好像它们被一个限定名引用一样,也就是说,忽略更多派生类中任何可能的虚拟覆盖析构函数。基和成员按照其构造函数完成的相反顺序被销毁(见 12.6.2)。析构函数中的 return 语句 (6.6.3) 可能不会直接返回给调用者;在将控制权转移给调用者之前,会调用成员和基的析构函数。

于 2013-06-13T17:54:02.437 回答
3

是的:你可以有virtual析构函数,唯一的原因是在派生类中重写它们。

它看起来像这样:

class Parent {
public:
    virtual ~Parent();
};

class Child : public Parent {
public:
    virtual ~Child();
};

class Grandchild : public Child {
public:
    ~Grandchild(); // virtual is inherited here
};

请注意,析构函数不像普通函数那样被名称覆盖,因为名称始终是您要销毁其实例的类的名称。

另请注意,父类的析构函数也总是被调用,因此您不需要复制它们的清理代码:详细阅读成员对象和基类子对象的构造和销毁顺序。


术语

  • 重写virtual函数意味着在派生类中实现基类函数。您根本无法更改签名(使用协变返回类型除外)。因此,覆盖总是与继承的虚函数具有相同的签名。
  • 重载一个函数意味着实现多个具有相同名称(并且在某种意义上相同范围)的函数。因此,重载总是与其他同名的重载具有不同的签名,与虚拟调度没有直接关系,也不一定是继承的。
于 2013-06-13T17:40:27.360 回答
0

是的; 每当您有一个可以使用对基类的引用来销毁的子类时,您都可以并且应该使析构函数成为虚拟的。如果您提供虚拟析构函数,静态代码分析工具甚至会抱怨。

考虑以下示例:

class A
{
public:
    A() { a = new int; }
    virtual ~A() { delete a; }

private:
    int *a;
};

class B : public A
{
public:
    B() { b = new int; }
    virtual ~B() { delete b; }

private:
    int *b;
};

int main()
{
    A *a = new B();
    delete a;
}

如果析构函数不是虚拟的,那么delete a只会调用 A 的析构函数,最终会导致内存泄漏。但是因为它是虚拟的,所以两个析构函数都会按照~B()->的顺序被调用~A()

于 2013-06-13T17:47:25.213 回答