在一个类中有没有声明虚析构函数有时候是有好处的,但是否需要特意避免编写虚析构函数呢?
在一个类中有没有声明虚析构函数有时候是有好处的,但是否需要特意避免编写虚析构函数呢?
下列情况之一为真时,无需使用虚析构函数:
除非你真的特别需要节省内存,否则没有特定的理由避免使用虚析构函数。
struct A {
// virtual ~A ();
int i;
int j;
};
void foo () {
A a = { 0, 1 }; // Will fail if virtual dtor declared
}
void bar (...);
void foo (A & a) {
bar (a); // Undefined behavior if virtual dtor declared
}
struct A {
A(int i, int j);
virtual ~A ();
int i;
int j;
};
void foo () {
A a = { 0, 1 }; // OK
}
只有在我有虚拟方法的情况下,才会声明虚拟析构函数。一旦我有虚拟方法,我就不相信自己可以避免在堆上实例化它或存储基类的指针。这两种操作非常常见,如果析构函数没有声明为虚拟函数,它们通常会默默地泄漏资源。
只要有可能在子类对象的指针上调用delete
函数,就需要一个虚析构函数。这可以确保在运行时正确地调用析构函数,而无需编译器在编译时知道堆上对象的类类型。例如,假设B
是A
的一个子类:
A *x = new B;
delete x; // ~B() called, even though x has type A*
如果你的代码不是性能关键的,为了安全起见,给你编写的每个基类都添加一个虚析构函数是合理的。
然而,如果你在紧密的循环中delete
了很多对象,调用虚函数(即使是空的)的性能开销可能会很大。编译器通常不能内联这些调用,处理器可能难以预测要去哪里。这不太可能对性能产生重大影响,但值得一提。
虚函数意味着每个分配的对象都会通过虚函数表指针增加内存成本。
因此,如果您的程序涉及分配大量某个对象,则值得避免所有虚函数以节省每个对象的额外32位。
在所有其他情况下,使析构函数虚拟将为自己节省调试痛苦。
并非所有的C++类都适合作为带有动态多态性的基类。
如果你想让你的类适合于动态多态性,那么它的析构函数必须是虚的。此外,任何子类可能想要覆盖的方法(这可能意味着所有公共方法,以及一些内部使用的受保护方法)都必须是虚的。
如果你的类不适合于动态多态性,则析构函数不应标记为虚的,因为这样做是误导性的。这只会鼓励人们错误地使用你的类。
以下是一个示例,即使其析构函数是虚的,也不适合用作动态多态性的基类:
class MutexLock {
mutex *mtx_;
public:
explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); }
~MutexLock() { mtx_->unlock(); }
private:
MutexLock(const MutexLock &rhs);
MutexLock &operator=(const MutexLock &rhs);
};
这个类的整个目的是为了在RAII中保持在堆栈上。如果你传递指向这个类对象的指针,更不用说它的子类了,那么你做错了。
如果您绝对必须确保您的类没有虚表,那么您也不能有虚析构函数。
这是一个罕见的情况,但确实会发生。
最熟悉的执行此操作的模式示例是DirectX D3DVECTOR和D3DMATRIX类。这些是类方法而不是函数,为了语法糖,但这些类故意没有虚表,以避免函数开销,因为这些类专门用于许多高性能应用程序的内部循环。
我通常会将析构函数声明为虚函数,但是如果您有内循环中使用的性能关键代码,则可能希望避免虚表查找。在某些情况下这可能非常重要,比如碰撞检测。但是,请注意在使用继承时如何销毁这些对象,否则您只会销毁对象的一半。
请注意,如果对象上有任何虚方法,则会为该对象执行虚表查找。因此,如果类中还有其他虚方法,则删除析构函数上的虚说明没有意义。