虚析构函数

“你有没有听说过虚析构函数?”——这是某面试官问我的第一个问题。

这貌似应该是一个学C++的人必知必会的知识点,可是很遗憾,我那时候的回答是“见过,可没深入理解它。”是的,这是我学过的知识,面试归来以后翻开 就可以看到在面向对象编程那章里面赫然有一节是专门讲它的。

面试官是明智的,这样一个问题足以考察出应聘者对于C++面向对象的熟练程度——实际上这个知识点应该是广泛应用的。一条很有益的最佳实践即为:即使析构函数没有工作要做,继续层次的根类也应该定义一个虚析构函数。

引用 中的话说:

删除指向动态分配对象的指针时,需要运行析构函数在释放对象的内存之前清除对象。处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。

如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必为虚函数。

用一个简单的例子来证明一下。设有如下类层次:

class Base
{
public:
~Base() { cout << “Base Destroyed.” << endl; }
};

class Derived : public Base
{
public:
~Derived() { cout << “Derived Destroyed.” << endl; }
};

class Der2 : public Derived
{
public:
~Der2() { cout << “Der2 Destroyed.” << endl; }
};

然后在程序某处有如下调用:

Base b;
Derived d;

则在控制台下产生的输出将为:

Derived Destroyed.
Base Destroyed.
Base Destroyed.

这是普通的处于栈内存中的对象的析构过程,前二行销毁d,最后一行销毁b。

如果有如下调用:

Derived*pBase2 = new Derived;
delete pBase2;

正如我们的猜想,在控制台的输出是:

Derived Destroyed.
Base Destroyed.

而如果将上面的调用改为:

Base *pBase2 = new Derived;
delete pBase2;

则在控制台下产生的输出将变为:

Base Destroyed.

这样就出现问题了,虽然pBase2指针指向的是一个派生类的对象,可是在delete时只是调用了基类的析构函数,即派生类特有的部分仍残留在内存中,也即出现了我们平常据说的内存泄漏。上述现象还说明在delete一个指向派生类对象的基类指针时,如果基类的析构函数不是virtual的,则在运行时无法像平时理解的多态中那样动态确定要析构的对象的真正类型。

我们在Base类的析构函数前加上virtual关键字试一下,可以发现控制台的输出变为:

Derived Destroyed.
Base Destroyed.

则正如 中所说的,没用virtual时的未定义行为现在有了正确的执行了。

那么基类的析构函数定义为virtual时,其派生类的析构函数是否继承了这一点呢?来个例子一试便知:设有如下调用

Base *pBase3 = new Der2;
delete pBase3

产生的控制台输出为:

Der2 Destroyed.
Derived Destroyed.
Base Destroyed.

是的,其派生类将基类析构函数中的虚属性继承了下来。

如上即为我对虚析构函数的理解与测试,可能比较肤浅,如其中有不准确的说法或者有更好的建议欢迎批评指正。

你可能感兴趣的:(编程,工作,面试,测试,delete,Class)