我需要显式调用基类虚析构函数吗?

435

在C++中覆盖类(包含虚析构函数)时,我在继承类中重写了析构函数并将其声明为虚函数,但是我需要调用基类的析构函数吗?

如果需要,我想代码应该是这样的...

MyChildClass::~MyChildClass() // virtual in header
{
    // Call to base destructor...
    this->MyBaseClass::~MyBaseClass();

    // Some destructing specific to MyChildClass
}

我是正确的吗?

7个回答

589

不需要手动调用析构函数,析构函数会自动按照构造函数的相反顺序被调用(基类的析构函数最后被调用)。不要手动调用基类的析构函数。


纯虚析构函数怎么样?我的链接器试图在继承类的非虚析构函数末尾调用它。 - cjcurrie
48
如果你想要一个没有实现体的纯虚析构函数,那么你需要给它一个空实现体。对于普通的纯虚函数,子类可以重载覆盖该函数,但对于析构函数,所有基类和派生类都会被调用,所以必须提供实现体。 "=0" 只是表示该函数必须被重载,因此如果需要,它仍然是有用的构造函数。 - Lou Franco
1
这个问题可能与questions/15265106/c-a-missing-vtable-error有关并且有所帮助。 - Paul-Sebastian Manole
为什么尼克·博尔顿的代码调用基类析构函数两次时不会导致分段错误,而在指向基类的指针上调用delete两次会导致分段错误? - Géry Ogam
4
任何错误的代码都不能保证会导致分段错误。此外,调用析构函数并不会释放内存。 - Lou Franco

106
不需要调用基类析构函数,基类析构函数会被派生类析构函数自动调用。请参见我的相关回答了解析构顺序。
为什么要在基类中声明虚析构函数,请看下面的代码:
class B
{
public:
    virtual ~B()
    {
        cout<<"B destructor"<<endl;
    }
};


class D : public B
{
public:
    virtual ~D()
    {
        cout<<"D destructor"<<endl;
    }
};

当你执行以下操作时:

B *pD = new D();
delete pD;

如果B类中没有虚析构函数,只有~B()会被调用。但是由于你在D类中声明了虚析构函数,所以首先会调用~D(),然后调用~B()。


26
好的,我会尽力进行翻译。需要翻译的内容是:"Please include the program (pseudo) output. It will help reader.",我的翻译如下:"请包含程序(伪代码)输出。这将有助于读者。" - Kuldeep Dhaka
@KuldeepSinghDhaka 读者可以在 https://wandbox.org/permlink/KQtbZG1hjVgceSlO 上实时查看。 - the swine

34

其他人说的都对,但也要注意在派生类中不必声明析构函数为虚函数。一旦你在基类中声明了析构函数为虚函数,所有派生类的析构函数都将是虚函数,无论你是否声明它们为虚函数。换句话说:

struct A {
   virtual ~A() {}
};

struct B : public A {
   virtual ~B() {}   // this is virtual
};

struct C : public A {
   ~C() {}          // this is virtual too
};

1
如果B没有声明为虚函数,那么C还是虚函数吗? - Will
6
当一个虚方法(任何虚方法,不仅限于析构函数)被声明为虚方法时,所有派生类中覆盖该方法的方法都会自动成为虚方法。在这种情况下,即使您没有声明B的析构函数为virtual,它仍然是虚析构函数,C也是一样。 - boycy
1
但是与其他重写方法不同,其名称和基类中对应方法的参数相同,析构函数的名称不同。这会有影响吗?@boycy - Yuan Wen
2
@YuanWen 不会的,(唯一的)派生析构函数总是覆盖其基类的(唯一的)析构函数。 - boycy

28

C++中的析构函数会在它们被构造的顺序(派生类在基类之后)中自动调用,但仅当基类的析构函数被声明为virtual时。

如果没有声明为virtual,则在对象删除时只会调用基类的析构函数。

例如:没有虚拟析构函数的情况

#include <iostream>

using namespace std;

class Base{
public:
  Base(){
    cout << "Base Constructor \n";
  }

  ~Base(){
    cout << "Base Destructor \n";
  }

};

class Derived: public Base{
public:
  int *n;
  Derived(){
    cout << "Derived Constructor \n";
    n = new int(10);
  }

  void display(){
    cout<< "Value: "<< *n << endl;
  }

  ~Derived(){
    cout << "Derived Destructor \n";
  }
};

int main() {

 Base *obj = new Derived();  //Derived object with base pointer
 delete(obj);   //Deleting object
 return 0;

}

输出

Base Constructor
Derived Constructor
Base Destructor

示例:使用基类虚析构函数

#include <iostream>

using namespace std;

class Base{
public:
  Base(){
    cout << "Base Constructor \n";
  }

  //virtual destructor
  virtual ~Base(){
    cout << "Base Destructor \n";
  }

};

class Derived: public Base{
public:
  int *n;
  Derived(){
    cout << "Derived Constructor \n";
    n = new int(10);
  }

  void display(){
    cout<< "Value: "<< *n << endl;
  }

  ~Derived(){
    cout << "Derived Destructor \n";
    delete(n);  //deleting the memory used by pointer
  }
};

int main() {

 Base *obj = new Derived();  //Derived object with base pointer
 delete(obj);   //Deleting object
 return 0;

}

输出

Base Constructor
Derived Constructor
Derived Destructor
Base Destructor

建议将基类析构函数声明为virtual,否则会导致未定义的行为。

参考:虚拟析构函数


1
这是关于虚拟或非虚拟的非常重要的注释。感谢您提供这个答案。 - Guy Nir

15

不,你从来没有调用基类的析构函数,正如其他人指出的那样,它总是自动调用的,但这里有一个概念证明和结果:

class base {
public:
    base()  { cout << __FUNCTION__ << endl; }
    ~base() { cout << __FUNCTION__ << endl; }
};

class derived : public base {
public:
    derived() { cout << __FUNCTION__ << endl; }
    ~derived() { cout << __FUNCTION__ << endl; } // adding call to base::~base() here results in double call to base destructor
};


int main()
{
    cout << "case 1, declared as local variable on stack" << endl << endl;
    {
        derived d1;
    }

    cout << endl << endl;

    cout << "case 2, created using new, assigned to derive class" << endl << endl;
    derived * d2 = new derived;
    delete d2;

    cout << endl << endl;

    cout << "case 3, created with new, assigned to base class" << endl << endl;
    base * d3 = new derived;
    delete d3;

    cout << endl;

    return 0;
}

输出结果为:

case 1, declared as local variable on stack

base::base
derived::derived
derived::~derived
base::~base


case 2, created using new, assigned to derive class

base::base
derived::derived
derived::~derived
base::~base


case 3, created with new, assigned to base class

base::base
derived::derived
base::~base

Press any key to continue . . .

如果将基类的析构函数声明为虚函数,那么情况3的结果将与情况1和2相同。


很好的说明。如果您尝试从派生类调用基类析构函数,您应该会收到类似于“error: no matching function for call to ‘BASE::BASE()’ <newline> ~BASE();” 的编译器错误。至少这是我使用的g++ 7.x编译器的行为。 - Kemin Zhou

11

不同于其他虚方法,在这里,您不需要显式地从派生类调用基类方法来“链接”调用,编译器会生成代码以按照构造函数调用的相反顺序调用析构函数。


7
不需要。它会自动调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接