我对大多数 OO 理论有了深刻的理解,但让我困惑的一件事是虚拟析构函数。
我认为无论什么以及链中的每个对象,析构函数总是会被调用。
你什么时候打算让它们成为虚拟的?为什么?
当您可能通过指向基类的指针删除派生类的实例时,虚拟析构函数很有用:
class Base
{
// some virtual methods
};
class Derived : public Base
{
~Derived()
{
// Do some important cleanup
}
};
在这里,您会注意到我没有声明 Base 的析构函数是virtual
。现在,我们来看看以下代码段:
Base *b = new Derived();
// use b
delete b; // Here's the problem!
由于 Base 的析构函数不是virtual
而b
是指向Derived
对象的Base*
,因此delete b
具有未定义的行为 :
[在
delete b
] 中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类, 静态类型应具有虚拟析构函数或者行为未定义 。
在大多数实现中,对析构函数的调用将像任何非虚拟代码一样被解析,这意味着将调用基类的析构函数,但不会调用派生类的析构函数,从而导致资源泄漏。
总而言之,总是让基类的析构函数在被多态操作时是virtual
。
如果要防止通过基类指针删除实例,可以使基类析构函数受保护且非虚拟; 通过这样做,编译器将不允许您在基类指针上调用delete
。
您可以在Herb Sutter 的这篇文章中了解有关虚拟性和虚拟基类析构函数的更多信息。
虚拟构造函数是不可能的,但虚拟析构函数是可能的。让我们实验......
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
上面的代码输出如下:
Base Constructor Called
Derived constructor called
Base Destructor called
派生对象的构造遵循构造规则,但是当我们删除 “b” 指针(基指针)时,我们发现只有基本析构函数被调用。但这不能发生。要做适当的事情,我们必须使基础析构函数成为虚拟的。现在让我们看看下面发生了什么:
#include <iostream>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
virtual ~Base(){
cout << "Base Destructor called\n";
}
};
class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};
int main()
{
Base *b = new Derived1();
delete b;
}
输出更改如下:
Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called
因此,基指针的破坏(在派生对象上进行分配!)遵循破坏规则,即首先导出然后是基数。另一方面,对于构造函数,没有像虚构造函数那样的东西。
在多态基类中声明析构函数是虚拟的。这是 Scott Meyers 的Effective C ++ 中的第 7 项。迈尔斯继续总结,如果一个类有任何虚函数,它应该有一个虚析构函数,而不是类设计为基类或不是设计用于多态不应该声明虚析构函数。