Answers:
如果满足以下任一条件,则无需使用虚拟析构函数:
没有特别的原因可以避免它,除非您真的如此被迫占用内存。
要明确地回答这个问题,即什么时候应该不声明一个虚析构函数。
C ++ '98 / '03
添加虚拟析构函数可能会将您的类从POD(普通旧数据) * 更改或聚合为非POD。如果您的类类型是在某处聚合初始化的,则这可能会阻止项目的编译。
struct A {
// virtual ~A ();
int i;
int j;
};
void foo () {
A a = { 0, 1 }; // Will fail if virtual dtor declared
}
在极端情况下,这种更改还可能导致未定义的行为,其中该类以需要POD的方式使用,例如,通过省略号参数传递该类或将其与memcpy一起使用。
void bar (...);
void foo (A & a) {
bar (a); // Undefined behavior if virtual dtor declared
}
[* POD类型是对其内存布局具有特定保证的类型。该标准实际上只是说,如果要从具有POD类型的对象复制到字符数组(或无符号字符)并再次返回,则结果将与原始对象相同。
现代C ++
在最新版本的C ++中,POD的概念被划分为类布局及其构造,复制和销毁。
对于省略号的情况,它不再是未定义的行为,现在已由实现定义的语义有条件地支持(N3937-〜C ++ '14-5.2.2 / 7):
...传递一个具有非平凡的复制构造函数,非平凡的移动构造函数或平凡的析构函数且没有相应参数的类类型的可能求值的参数(第9条),该实现有条件地受到支持-定义的语义。
声明除析构函数以外的其他方法=default
将意味着它并非无关紧要(12.4 / 5)
...如果不是用户提供的,则析构函数很简单...
对Modern C ++的其他更改减少了聚合初始化问题的影响,因为可以添加构造函数:
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
在一个紧密的循环中使用了许多对象,则调用虚拟函数(甚至是一个空函数)的性能开销可能会很明显。编译器通常不能内联这些调用,并且处理器可能很难预测到哪里。这不太可能对性能产生重大影响,但是值得一提。
并非所有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的堆栈上。如果您要传递指向此类对象的指针,更不用说它的子类了,那么您就做错了。
不将析构函数声明为虚拟的一个很好的理由是,这可以避免类添加虚拟函数表,并且应尽可能避免这样做。
我知道许多人宁愿总是为了安全起见始终将析构函数声明为虚拟的。但是,如果您的类没有任何其他虚函数,那么拥有虚拟析构函数就没有任何意义。即使您将您的课程提供给其他人,然后再从该课程中派生其他课程,他们也没有理由在对您的课程cast之以鼻的指针上调用delete-如果他们这样做了,我认为这是一个错误。
好的,只有一个例外,即如果您的类被(错误地)用于执行派生对象的多态删除,但是您(或其他人)希望知道这需要一个虚拟析构函数。
换句话说,如果您的类具有非虚拟的析构函数,那么这是一个非常明确的声明:“不要用我来删除派生对象!”
性能答案是我所知道的唯一一个有可能成为现实的答案。如果您已经测量并发现对析构函数进行虚拟化确实可以加快处理速度,那么该类中可能还需要加快处理速度,但是在这一点上,还有一些重要的考虑因素。总有一天,有人会发现您的代码将为他们提供一个不错的基类,并节省了他们一周的工作。您最好确保他们完成那一周的工作,复制并粘贴您的代码,而不要使用您的代码作为基础。您最好确保将一些重要的方法设为私有,这样任何人都无法从您那里继承。