默认析构函数能自动生成为虚拟析构函数吗?

63

默认析构函数可以自动生成为虚拟析构函数吗?

如果我定义了一个基类,但没有默认析构函数,那么是否会自动生成默认的虚拟析构函数?


1
顺便问一下,什么是默认析构函数?是否有多种类型的析构函数? - user88637
4
如果在一个类中没有声明析构函数,那么编译器会为您插入一个。除了一些不寻常的情况可能会有差异,否则这个“默认析构函数”与您定义的“~MyClass() {}”是相同的。 - Steve Jessop
3
确切地说:public: ~MyClass() {} - 即使类成员默认为私有。 - MSalters
2
一个带有非虚析构函数的基类和一个带有虚析构函数的派生类感觉像是内置设计缺陷。 - Spencer
8个回答

58

不需要。定义虚方法会带来额外的成本,而C++的哲学是不让你为未明确使用的东西付出代价。如果虚析构函数自动生成,你将自动支付代价。

那么为什么不只是定义一个空的虚析构函数呢?


71
请注意,自 C++11 起,您还可以使用 virtual ~Foo() = default; 的方式(如果最近有人阅读本文) - Cory Kramer
1
如果该类有任何其他虚函数,会有什么额外的成本呢?你能举个例子吗? - Spencer
2
@Spencer:调用析构函数需要通过对象的虚表在运行时解析d-tor代码的地址。对于非虚拟析构函数,代码的地址在编译时已知。 - Marco Freudenberger
一个带有虚析构函数的基类和一个派生类带有非虚函数,感觉像是一个内置的设计缺陷。 - Spencer
1
@Spencer:如果基类声明了(隐式或显式)虚析构函数,则派生类的析构函数会被隐式声明为虚函数。 如果你的基类有一个非虚析构函数,那么从它继承并添加新成员是很危险的,因为如果你使用基类类型的指针调用delete,这些成员的析构函数将永远不会被调用! - Andreas H.
显示剩余2条评论

55

在C++11中,您可以使用:

class MyClass
{
  // create a virtual, default destructor
  virtual ~MyClass() = default;
};

这个程序可以使用“icpc”编译,但无法使用“g++-4.6.3”编译,并显示错误信息:“error: ˜virtual MyClass::~MyClass() declared virtual cannot be defaulted in the class body”。看起来很明确,他们不希望你这样做;是否有一个版本的“g++”可以使用这个程序? - user14717
1
@NickThompson,我已经成功地在gcc 4.8.1和4.9.0上使用过这个。似乎在4.6中也支持。你是否使用了-std=c++0x编译? - Drew Noakes
我不确定那里发生了什么。 - user14717
3
在这里使用default与使用空构造函数{}有什么区别? - Zitrax
1
@Zitrax 我相信你是想说“析构函数”,而不是“构造函数”。请注意,对于构造函数,一个空的用户提供的构造函数和默认的构造函数(使用= default)之间确实有区别。区别在于前者会阻止该类成为聚合类,而后者则不会。聚合类可以使用花括号语法进行初始化,这有时可能很有用/需要。但是,请注意,还有其他细节可能会阻止类成为聚合类(最显著的是虚方法)。 - bartgol
1
想一想,使用=default作为析构函数的原因是有道理的,至少对于非虚拟情况下的情况:它允许该类不仅是聚合体,而且是一个Plain Old Data(POD)结构体(假设其他要求已满足)。用户提供的空析构函数已经防止您拥有POD结构。 - bartgol

12

是的,通过继承具有虚析构函数的基类来实现。在这种情况下,您已经为多态类(例如vtable)付出了代价。


9
不,所有的析构函数默认情况下都不是虚拟的。您需要在所有基类上定义一个虚拟析构函数。
此外,引用Scott Meyers在他的书“Effective C++”中的话:
C++语言标准在这个主题上非常清晰。当您尝试通过基类指针删除派生类对象并且基类具有非虚拟析构函数(如EnemyTarget),结果是未定义的。
实际上,如果您认为某人可能最终从它创建一个派生类,那么通常最好定义一个带有虚拟析构函数的类。我倾向于让所有类都具有虚拟析构函数。是的,这样做会有一些成本,但不使其虚拟化的代价往往超过微不足道的运行时开销。
我建议,只有在您绝对确定要这样做时才将其设置为非虚拟,而不是依赖编译器强制执行的默认非虚拟。您可能会持不同意见,但总之,最近我在一些已经存在数年的遗留代码中有一个可怕的内存泄漏,我只是在其中添加了一个std::vector。结果发现其中一个基类没有定义析构函数(默认析构函数为空,非虚拟!),因为以前没有像这样分配内存,所以没有泄漏内存。经过多天的调查和浪费时间...

1
如果我正确理解了情况,您的代码在更改之前已经存在未定义行为(尽管没有内存泄漏):通过基类指针进行删除时,派生对象会在本身被销毁之前被其基类销毁。缺少需要销毁的其他数据成员并不能使行为变得定义。因此,您的调查工作不是无用功... - Marc van Leeuwen

8

Uri和Michael是正确的 - 我只想补充一点,如果你不喜欢要触碰两个文件来声明和定义析构函数,那么在头文件中内联定义一个最小化的析构函数是完全可以的:

class MyClass
{
   // define basic destructor right here
   virtual ~MyClass(){}

   // but these functions can be defined in a different file
   void FuncA();
   int FuncB(int etc);
}

实际上,我认为当你链接这个时,你会得到一个对MyClass虚表的未定义引用。 - keraba
2
只有在使用GCC并且未将FuncA和FuncB定义为非内联时,才会出现“undefined reference to vtable”错误,这是因为GCC未能发出所有必要的内容以允许正确链接。 - Rob Kennedy
我对此感到惊讶,为什么会导致链接问题?有人能详细说明一下吗? - Uri
3
我常常这样做,在 MSVC 中链接得很好,更不用说 C++ 标准明确允许这样做。我猜这是 GCC 的一个 bug。 - Crashworks

2

目前,Uri是正确的。但是,在您的类中声明虚拟方法后,您仍然要为虚拟表的存在付出代价。实际上,如果您的类具有虚拟方法,但没有虚拟析构函数,编译器会发出警告。这可能成为自动生成默认虚拟析构函数的候选项,而不是烦人的警告。


1
不行,你需要将它声明为虚函数。

0

有些人说它从不默认为虚拟的。这并不完全正确。

尽管析构函数不会被继承,但如果基类声明其析构函数为虚拟的,则派生析构函数始终会覆盖它。

这意味着如果基类有一个虚拟析构函数,您不必将派生类的析构函数定义为虚拟的,也不必显式编写:

/// All of these are unnecessary and won't change the destruction behavior
/// as long as Class inherits from a base class with a virtual destructor
virtual ~Class() = default;
// or
~Class() override = default;
// or
~Class() {}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接