C++11 中具有虚拟成员的虚拟析构函数
Virtual destructor with virtual members in C++11
在这些关于C++11/14标准的幻灯片中,在第15张幻灯片上,作者写道,"许多经典的编码规则不再适用"C++11。他提出了三个例子的清单,我同意三法则和内存管理。
然而,他的第二个例子是"具有虚拟成员的虚拟析构函数"(仅此而已)。什么意思?我知道必须将基类析构函数声明为虚拟,以便调用正确的析构函数,如果我们有类似的东西
Base *b = new Derived;
...
delete b;
这里对此有很好的解释:何时使用虚拟析构函数?
但是,如果您有虚拟成员,现在在 C++11 中声明虚拟析构函数是没有用的吗?
作为幻灯片的作者,我将尝试澄清。
如果您编写的代码显式分配带有 new
的 Derived
实例并使用基类指针通过 delete
销毁它,则需要定义一个virtual
析构函数,否则最终会导致不完全销毁Derived
实例。但是,我建议完全放弃new
和delete
,并专门使用shared_ptr
来引用堆分配的多态对象,例如
shared_ptr<Base> pb=make_shared<Derived>();
这样,共享指针会跟踪要使用的原始析构函数,即使shared_ptr<Base>
用于表示它。一旦最后一个引用shared_ptr
超出范围或被重置,将调用~Derived()
并释放内存。因此,您无需~Base()
虚拟
unique_ptr<Base>
和 make_unique<Derived>
不提供此功能,因为它们不提供与删除程序相关的shared_ptr
机制,因为唯一指针要简单得多,并且旨在实现最低开销,因此不会存储删除程序所需的额外函数指针。对于unique_ptr
,删除器函数是类型的一部分,因此具有引用~Derived
的删除器的uniqe_ptr将与使用默认删除器的unique_ptr<Base>
不兼容,如果~Base
不是虚拟的,这对于派生实例来说无论如何都是错误的。
我提出的个人建议旨在易于遵循和一起遵循。他们尝试通过让库组件和编译器生成的代码完成所有资源管理来生成更简单的代码。
在类中定义(虚拟)析构函数将禁止编译器提供的移动构造函数/赋值运算符,并且可能还会禁止编译器提供的复制构造函数/赋值运算符在C++的未来版本中。使用=default
复活它们变得很容易,但看起来仍然像很多样板代码。最好的代码是你不必编写的代码,因为它不会出错(我知道该规则仍有例外)。
总结一下"不要定义(虚拟)析构函数"作为我的"零法则"的推论:
每当在现代C++中设计多态 (OO) 类层次结构并希望/需要在堆上分配其实例并通过基类指针访问它们时,请使用 make_shared<Derived>()
来实例化它们并shared_ptr<Base>
保留它们。这使您可以保留"零规则"。
这并不意味着您必须在堆上分配所有多态对象。例如,定义一个以(Base&)
为参数的函数,可以使用局部Derived
变量毫无问题地调用,并且对于Base
的虚成员函数将表现为多态。
在我看来,动态OO多态性在许多系统中被严重过度使用。当我们使用C++时,我们不应该像Java那样编程,除非我们遇到问题,其中堆分配对象的动态多态性是正确的解决方案。
这与演示文稿中其他地方提到的"零规则"有关。
如果您只有自动成员变量(即对否则将是原始指针的成员使用 shared_ptr
或 unique_ptr
),那么您不需要编写自己的副本或移动构造函数或赋值运算符 - 编译器提供的默认值将是最佳的。使用类内初始化,也不需要默认构造函数。最后,你根本不需要编写析构函数,无论是否虚拟。
链接的论文显示了相关代码:
std::unique_ptr<Derived> { new Derived };
存储的删除器是std::default_delete<Derived>
的,这不需要Base::~Base
是虚拟的。
现在你可以把它移到 unique_ptr<Base>
,它还将移动std::default_delete<Derived>
而不将其转换为std::default_delete<Base>
.
要回答具体问题...
但是,如果您有虚拟成员,现在在 C++11 中声明虚拟析构函数是否毫无用处?
在 C++11 核心语言中,对虚拟析构函数的需求没有改变。如果要使用基指针删除派生对象,则必须将析构函数声明为 virtual。
幻灯片中的陈述给人的印象是,C++11 以某种方式改变了与虚拟析构函数相关的行为 - 事实并非如此。正如作者所澄清的那样,它仅适用于使用 shared_ptr
.但是仍然需要一个虚拟析构函数的事实(除了使用shared_ptr
)在冗长的解释中被稀释了。
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 是否可以使用函数指针调用虚拟析构函数?
- 在没有动态内存的世界中,我是否需要虚拟析构函数?
- 程序永远不会进入虚拟析构函数
- C++ std::vector 中的虚拟析构函数继承
- 哪种方法更适合处理虚拟析构函数?
- 拥有"受保护的非虚拟析构函数"与"受保护虚拟析构构函数"有什么好处
- 带有未解析外部元素的C++虚拟析构函数
- 即使基类和派生类只使用基元数据类型,我是否需要定义虚拟析构函数
- C++切片和虚拟析构函数
- C++虚拟继承、虚拟析构函数和 dynamic_cast<void*>
- 添加虚拟析构函数会使代码大小膨胀
- 应该是虚拟析构函数吗?但是怎么做呢?
- 虚拟析构函数将对象移出 rodata 部分
- 如何将 std::unique_ptr<Parent> 与具有受保护虚拟析构函数的只读父类一起使用
- DIRECTX9 中自定义顶点的虚拟析构函数
- 为什么缺少虚拟析构函数不会导致内存泄漏?
- std::unique_ptr 在虚拟析构函数上重置 SIGABRT
- C++11 中默认纯虚拟析构函数的正确放置
- 在派生类中重写哪个基类的虚拟析构函数