虚析构函数、虚赋值函数以及虚函数重写
本节阅读量:虚析构函数
尽管 C++ 会为类提供默认析构函数,但有时您会希望提供自己的析构函数,尤其是在类需要释放内存时。如果这个类会参与继承,就应该始终让析构函数成为虚函数。考虑以下示例:
|
|
注意:如果编译上面的示例,编译器可能会警告您有关非虚析构函数的信息(这是本例中有意设置的)。您可能需要禁用「将警告视为错误」的编译器选项才能编译通过。
由于 base 是 Base 指针,因此执行 “delete base;” 时,程序会检查 Base 的析构函数是否是虚函数。它不是,所以程序会认为只需要调用 Base 析构函数。我们可以从上面示例的输出中看到这一点:
|
|
然而,我们真正希望 delete 调用 Derived 的析构函数(随后它会调用 Base 的析构函数),否则 m_array 将不会被释放。将 Base 的析构函数设置为 virtual 即可实现这一点:
|
|
现在,该程序产生以下结果:
|
|
和普通 virtual 成员函数一样,如果基类函数是虚函数,则所有派生类中的重写都会被视为虚函数。没有必要仅仅为了标记 virtual 而创建空的派生类析构函数。
请注意,如果希望基类具有一个空的虚析构函数,可以这样定义:
|
|
虚赋值函数
赋值运算符也可以成为 virtual。然而,与析构函数不同,virtual 赋值运算符很容易引入大量 bug,并涉及一些超出本教程范围的高级主题。因此,为了简单起见,我们建议您暂时不要考虑这种情况。
忽略virtual
在少数情况下,您可能希望绕过函数的虚函数解析。例如,考虑以下代码:
|
|
在某些情况下,您可能希望指向 Derived 对象的 Base 指针调用 Base::getName(),而不是 Derived::getName()。为此,只需使用作用域解析运算符:
|
|
您可能不会经常使用这种写法,但至少应该知道它可以实现。
应该让所有的析构函数都是virtual的吗?
这是新程序员常见的问题。如上面的示例所示,如果基类析构函数未标记为 virtual,之后又通过指向派生对象的基类指针删除对象,程序就有内存泄漏的风险。避免这种情况的一种方法是将所有析构函数都标记为 virtual。但应该这样做吗?
回答“是”很容易,因为这样以后就可以把任何类当作基类使用。但这样做会降低性能(会为类的每个实例添加 virtual 指针)。因此,你必须在成本和设计意图之间做权衡。
我们的建议如下:如果类没有明确设计为基类,那么通常最好不要包含虚成员和虚析构函数。该类仍然可以通过组合来使用。如果类被设计为基类,或者具有任何虚函数,那么它应该始终具有虚析构函数。
如果决定使类不可继承,那么下一个问题是,是否可以强制执行这一点。
传统经验(正如备受推崇的 C++ 专家 Herb Sutter 最初提出的那样)建议这样避免非虚析构函数造成的内存泄漏:“基类析构函数应该是 public 且 virtual 的,或者是 protected 且非 virtual 的。”如果基类析构函数是 protected,就不能通过基类指针删除对象,这会阻止通过基类指针删除派生类对象。
不幸的是,这也阻止了外部对基类析构函数的任何使用。这意味着:
- 我们不应该动态分配基类对象,因为没有常规方法删除它们(虽然有非常规的变通方法,但并不理想)。
- 我们甚至不能静态分配基类对象,因为当它们超出作用域时,析构函数是不可访问的。
换句话说,使用这种方法时,为了让派生类安全,我们会让基类本身几乎不可用。
既然 final 说明符已经引入到语言中,我们的建议如下:
- 如果希望类可以被继承,请确保析构函数是 virtual 且 public 的。
- 如果您不希望任何人继承您的类,请将类标记为 final。这会直接防止其他类继承它,而不会对类本身施加其他使用限制。