c ++ - 什么时候不应该使用虚拟析构函数?

是否有充分的理由不为类声明虚拟析构函数? 什么时候应该特别避免写一个?

12个解决方案
68 votes

如果满足以下任何条件,则无需使用虚拟析构函数:

  • 无意从中派生类
  • 堆上没有实例化
  • 无意存储在超类的指针中

没有特别的理由要避免它,除非你真的如此紧迫的记忆。

sep answered 2019-08-13T10:35:18Z
66 votes

要明确回答这个问题,即何时不应声明虚拟析构函数。

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
}
Richard Corden answered 2019-08-13T10:37:01Z
25 votes

当且仅当我有虚拟方法时,我声明一个虚拟析构函数。 一旦我有虚拟方法,我就不相信自己避免在堆上实例化它或存储指向基类的指针。 这两个都是非常常见的操作,如果未将析构函数声明为虚拟,则通常会以静默方式泄漏资源。

Andy answered 2019-08-13T10:37:27Z
6 votes

只要有可能在指向具有类类型的子类的对象的指针上调用24789161616992279552,就需要虚拟析构函数。 这样可以确保在运行时调用正确的析构函数,而编译器不必在编译时知道堆上对象的类。 例如,假设BA的子类:

A *x = new B;
delete x;     // ~B() called, even though x has type A*

如果您的代码不是性能关键,那么为您编写的每个基类添加一个虚拟析构函数是合理的,只是为了安全起见。

但是,如果您在紧密循环中发现了很多对象,那么调用虚函数(即使是空的)的性能开销可能会很明显。 编译器通常不能内联这些调用,处理器可能很难预测到哪里去。 这不太可能对性能产生重大影响,但值得一提。

Jay Conrod answered 2019-08-13T10:38:07Z
5 votes

虚函数意味着每个分配的对象都会通过虚函数表指针增加内存开销。

因此,如果您的程序涉及分配大量的某些对象,则值得避免所有虚函数以便为每个对象保存额外的32位。

在所有其他情况下,您将节省自己的调试痛苦,使dtor虚拟化。

mxcl answered 2019-08-13T10:38:46Z
5 votes

并非所有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的堆栈上。 如果您传递指向此类对象的指针,更不用说它的子类,那么您就是在做错了。

Steve Jessop answered 2019-08-13T10:39:40Z
4 votes

不将析构函数声明为虚拟的一个很好的理由是,这样可以节省您的类添加虚拟函数表,并且应尽可能避免使用它。

我知道很多人宁愿总是将析构函数声明为虚拟,只是为了安全起见。 但是如果你的类没有任何其他虚函数那么真的,真的没有必要使用虚拟析构函数。 即使你把你的课提供给其他人然后从中派生出其他类,那么他们就没有理由在你的课堂上向你的班级调用删除指针 - 如果他们这样做了,我会认为这是一个错误。

好的,有一个例外,即如果您的类(错误地)用于执行派生对象的多态删除,但是您 - 或其他人 - 希望知道这需要虚拟析构函数。

换句话说,如果你的类有一个非虚拟析构函数,那么这是一个非常明确的陈述:"不要用我来删除派生对象!"

kidfisto answered 2019-08-13T10:40:28Z
3 votes

如果你有一个非常小的类,有大量的实例,vtable指针的开销会对程序的内存使用产生影响。 只要您的类没有任何其他虚拟方法,使析构函数非虚拟将节省开销。

Mark Ransom answered 2019-08-13T10:40:54Z
1 votes

我通常声明析构函数是虚拟的,但是如果你有内部循环中使用的性能关键代码,你可能想要避免虚拟表查找。 在某些情况下,这可能很重要,例如碰撞检查。 但是如果使用继承,请注意如何销毁这些对象,否则只会破坏对象的一半。

请注意,如果该对象上的任何方法是虚拟的,则会对对象进行虚拟表查找。 因此,如果在类中有其他虚拟方法,则无需删除析构函数上的虚拟规范。

Jørn Jensen answered 2019-08-13T10:41:26Z
1 votes

如果您绝对肯定必须确保您的班级没有vtable,那么您也不能拥有虚拟析构函数。

这是一种罕见的情况,但它确实发生了。

最常见的模式示例是DirectX D3DVECTOR和D3DMATRIX类。 这些是类方法而不是语法糖的函数,但是这些类故意没有vtable以避免函数开销,因为这些类专门用于许多高性能应用程序的内部循环中。

Lisa answered 2019-08-13T10:42:07Z
0 votes

关于将在基类上执行的操作以及应该虚拟操作的操作应该是虚拟的。 如果可以通过基类接口以多态方式执行删除,那么它必须虚拟地行为并且是虚拟的。

如果您不打算从类派生,则析构函数不需要是虚拟的。 即使你这样做,如果不需要删除基类指针,受保护的非虚拟析构函数也同样出色。

icecrime answered 2019-08-13T10:42:39Z
-7 votes

性能答案是我所知道的唯一有可能成为现实的答案。 如果你已经测量过并发现你的析构函数去虚拟化确实加快了速度,那么你可能在该类中还有其他需要加速的东西,但此时还有更重要的考虑因素。 有一天,有人会发现你的代码会为他们提供一个很好的基类,并为他们节省一周的时间。 您最好确保他们执行该周的工作,复制和粘贴您的代码,而不是使用您的代码作为基础。 您最好确保将某些重要方法设为私有,这样任何人都无法继承您。

Windows programmer answered 2019-08-13T10:43:05Z
translate from https://stackoverflow.com:/questions/300986/when-should-you-not-use-virtual-destructors