重写析构函数C++

Override Destructor C++

本文关键字:C++ 析构函数 重写      更新时间:2023-10-16

来自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,并在我选择的稍后时间显式调用它们的析构函数。

我可能在单词的语法上分裂头发

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

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

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

Parent const& p = Child();

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

是的,可以重写类的析构函数。 实际上,在定义使用多态性的类层次结构时,必须在基类中声明虚拟析构函数。

析构函数的重写的工作方式与普通成员函数的重写的工作方式完全相同,因为当您通过指向基类的指针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()
}

。调用 a->Foo() 时,将调用 BFoo()的方法。 由于B::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(。析构函数中的返回语句 (6.6.3( 可能不会直接返回给调用方;在移交控制权之前 对于调用方,将调用成员和基的析构函数。 数组元素的析构函数按与 它们的构造(见12.6(。

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

它看起来像这样:

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

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

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


术语

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

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

请考虑以下示例:

class A
{
public:
    A() { a = new int; }
    virtual ~A() { delete a; }
private:
    int *a;
};
class B final : public A
{
public:
    B() { b = new int; }
    ~B() { delete b; }
private:
    int *b;
};
int main()
{
    A *a = new B();
    delete a;
}
如果 A 的析构

函数不是虚拟的,那么delete a只会调用 A 的析构函数,并且最终会出现内存泄漏。但由于它是虚拟的,因此将按 -> ~A() ~B() 的顺序调用这两个析构函数。