我应该默认虚拟析构函数吗?

Should I default virtual destructors?

本文关键字:析构函数 虚拟 默认 我应该      更新时间:2023-10-16

我有一个抽象类,声明如下:

class my_type {
public:
    virtual ~my_type() = default;
    virtual void do_something() = 0;
};

使用 default 关键字像这样声明析构函数是否被认为是一种良好做法?有没有更好的方法?

另外,= 0是指定无默认实现的现代 (C++11( 方法,还是有更好的方法?

是的,您绝对可以将= default用于此类析构函数。特别是如果你只是要用{}替换它.我认为= default更好,因为它更明确,所以它立即引起了人们的注意,没有留下任何怀疑的余地。

但是,在这样做时需要考虑以下几点。

当你在头文件中= default析构函数(参见编辑((或任何其他与此相关的特殊函数(时,它基本上是在头文件中定义它。设计共享库时,您可能希望显式地仅由库而不是标头中提供析构函数,以便将来可以更轻松地更改它,而无需重新生成依赖二进制文件。但同样,当问题不仅仅是= default还是{}时。


编辑:正如肖恩在评论中敏锐地指出的那样,您还可以在类声明之外使用= default,这在这里得到了两全其美的效果。


另一个关键的技术区别是,该标准规定无法生成的显式默认函数将根本不生成。请考虑以下示例:

struct A { ~A() = delete; };
struct B : A { ~B() {}; }

不会编译,因为您强制编译器为 B 的析构函数生成指定的代码(及其隐式要求,例如调用 A 的析构函数(,而它不能,因为 A 的析构函数被删除了。但是,请考虑以下情况:

struct A { ~A() = delete; };
struct B : A { ~B() = default; }

事实上,这将进行编译,因为编译器看到无法生成~B(),因此它根本不生成它 - 并将其声明为已删除。这意味着只有在尝试实际使用/调用B::~B()时才会收到错误。

这至少有两个你应该注意的含义:

  1. 如果您希望在编译包含类声明的任何内容时获得错误,则不会得到它,因为它在技术上是有效的。
  2. 如果您最初总是将 = default 用于此类析构函数,那么您不必担心超类的析构函数被删除,如果事实证明它没问题并且您从未真正使用它。这是一种奇特的用途,但从这个意义上说,它更正确,更面向未来。只有当你真正使用析构函数时,你才会得到错误 - 否则,你将独自一人。

因此,如果您要采用防御性编程方法,您可能需要考虑简单地使用 {} .否则,您最好= default ing,因为这更好地遵守了获取正确、有效的代码库所需的最小编程指令,并避免意外后果1.


至于= 0:是的,这仍然是正确的方法。但请注意,它实际上并没有指定"没有默认实现",而是 (1( 该类不可实例化;(2( 任何派生类都必须重写该函数(尽管它们可以使用超类提供的可选实现(。换句话说,您既可以定义一个函数,也可以将其声明为纯虚拟函数。下面是一个示例:

struct A { virtual ~A() = 0; }
A::~A() = default;

这将确保对 A(及其析构函数(进行这些约束。


1(为什么这会以意想不到的方式有用的一个很好的例子是,有些人总是使用带有括号的return,然后C++14添加了decltype(auto),这基本上在使用它之间产生了技术差异带括号和不带括号。