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

C++ 什么时候不应该使用虚拟析构函数?,c++,virtual-functions,virtual-destructor,C++,Virtual Functions,Virtual Destructor,有没有理由不为类声明虚拟析构函数?什么时候应该特别避免编写虚拟析构函数?如果满足以下任一条件,则无需使用虚拟析构函数: 无意从中派生类 堆上没有实例化 无意存储在超类的指针中 除非您的内存非常紧张,否则没有具体的理由避免它。我声明了一个虚拟析构函数,当且仅当我有虚拟方法时。一旦有了虚拟方法,我就不相信自己能避免在堆上实例化它或存储指向基类的指针。这两种操作都是非常常见的操作,如果析构函数未声明为虚拟,则通常会悄悄泄漏资源。只要有可能在指向类类型的子类对象的指针上调用delete,就需要虚拟析

有没有理由不为类声明虚拟析构函数?什么时候应该特别避免编写虚拟析构函数?

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

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

除非您的内存非常紧张,否则没有具体的理由避免它。

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

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

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

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


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

性能问题的答案是我所知道的唯一有可能成为事实的答案。如果您已经测量并发现反虚拟化析构函数确实加快了速度,那么您可能在该类中还有其他需要加快的事情,但在这一点上还有更重要的考虑因素。总有一天,有人会发现你的代码可以为他们提供一个很好的基类,从而节省他们一周的工作量。你最好确保他们完成了那周的工作,复制和粘贴你的代码,而不是把你的代码作为基础。你最好确保你的一些重要方法是私有的,这样就没有人可以从你那里继承了。

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


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

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

C++'98/'03

添加虚拟析构函数可能会将类从*或聚合更改为非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)有条件地支持:

…传递具有非平凡复制构造函数、非平凡移动构造函数或on平凡析构函数且没有相应参数的类类型(第9条)的潜在求值参数时,实现定义的语义会有条件地支持

声明除
=default
之外的析构函数将意味着它不是微不足道的(12.4/5)

。。。如果析构函数不是由用户提供的,那么它是微不足道的

<> P>其他现代C++的更改减少了聚合初始化问题的影响,可以添加一个构造函数:

struct A {
  A(int i, int j);
  virtual ~A ();
  int i;

  int j;
};
void foo () { 
  A a = { 0, 1 };  // OK
}

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

因此,如果您的程序需要分配大量的对象,那么为了节省每个对象额外的32位,应该避免使用所有虚拟函数


在所有其他情况下,您将省去调试错误,使Dor虚拟化。

< P>不完全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坐在堆栈上。如果要传递指向此类对象的指针,更不用说子类了