我应该默认虚拟析构函数吗?
Should I default virtual destructors?
我有一个抽象类,声明如下:
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()
时才会收到错误。
这至少有两个你应该注意的含义:
- 如果您希望在编译包含类声明的任何内容时获得错误,则不会得到它,因为它在技术上是有效的。
- 如果您最初总是将
= default
用于此类析构函数,那么您不必担心超类的析构函数被删除,如果事实证明它没问题并且您从未真正使用它。这是一种奇特的用途,但从这个意义上说,它更正确,更面向未来。只有当你真正使用析构函数时,你才会得到错误 - 否则,你将独自一人。
因此,如果您要采用防御性编程方法,您可能需要考虑简单地使用 {}
.否则,您最好= default
ing,因为这更好地遵守了获取正确、有效的代码库所需的最小编程指令,并避免意外后果1.
至于= 0
:是的,这仍然是正确的方法。但请注意,它实际上并没有指定"没有默认实现",而是 (1( 该类不可实例化;(2( 任何派生类都必须重写该函数(尽管它们可以使用超类提供的可选实现(。换句话说,您既可以定义一个函数,也可以将其声明为纯虚拟函数。下面是一个示例:
struct A { virtual ~A() = 0; }
A::~A() = default;
这将确保对 A(及其析构函数(进行这些约束。
1(为什么这会以意想不到的方式有用的一个很好的例子是,有些人总是使用带有括号的return
,然后C++14添加了decltype(auto)
,这基本上在使用它之间产生了技术差异带括号和不带括号。
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 是否可以使用函数指针调用虚拟析构函数?
- 在没有动态内存的世界中,我是否需要虚拟析构函数?
- "虚拟""覆盖"析构函数
- 程序永远不会进入虚拟析构函数
- C++ std::vector 中的虚拟析构函数继承
- 哪种方法更适合处理虚拟析构函数?
- 拥有"受保护的非虚拟析构函数"与"受保护虚拟析构构函数"有什么好处
- 带有未解析外部元素的C++虚拟析构函数
- 即使基类和派生类只使用基元数据类型,我是否需要定义虚拟析构函数
- 从内部类的析构函数调用虚拟函数
- C++切片和虚拟析构函数
- C++虚拟继承、虚拟析构函数和 dynamic_cast<void*>
- 添加虚拟析构函数会使代码大小膨胀
- 应该是虚拟析构函数吗?但是怎么做呢?
- 虚拟析构函数将对象移出 rodata 部分
- 为什么虚拟类的析构函数不会自动添加到 vtable 中?
- 如何将 std::unique_ptr<Parent> 与具有受保护虚拟析构函数的只读父类一起使用
- DIRECTX9 中自定义顶点的虚拟析构函数
- 声明析构函数虚拟就足够了吗?