关于c ++:在C ++ 11中使用虚拟成员的虚拟析构函数

Virtual destructor with virtual members in C++11

在有关C++ 11/14标准的幻灯片中,在幻灯片15上,作者写道"C++中的许多经典编码规则不再适用"。他提出了三个例子的清单,我同意三法则和记忆管理。

然而,他的第二个例子是"带有虚拟成员的虚拟析构函数"(仅此而已)。这是什么意思?我知道必须将基类析构函数声明为虚的,以便在有类似

1
2
3
Base *b = new Derived;
...
delete b;

这里很好地解释了:何时使用虚拟析构函数?

但是,如果你有虚拟成员,C++ 11中声明虚拟析构函数是没有用的吗?


作为幻灯片的作者,我将尽力澄清。

如果用new显式地分配Derived实例,并用基类指针用delete销毁该实例,则需要定义virtual析构函数,否则将导致无法完全销毁Derived实例。但是,我建议完全不使用newdelete,只使用shared_ptr来指堆分配的多态对象,例如

1
shared_ptr<Base> pb=make_shared<Derived>();

这样,共享指针就可以跟踪要使用的原始析构函数,即使使用shared_ptr来表示它。一旦最后一个引用的shared_ptr超出范围或被重置,将调用~Derived()并释放内存。因此,您不需要使~Base()虚拟化。

unique_ptrmake_unique不提供此功能,因为它们不提供shared_ptr关于删除程序的机制,因为唯一指针简单得多,旨在降低开销,因此不存储删除程序所需的额外函数指针。使用unique_ptr时,deleter函数是类型的一部分,因此,带有引用~Derived的deleter的uniqe ptr将与使用默认deleter的unique_ptr不兼容,如果~Base不是虚拟的,这对于派生实例来说是错误的。

我提出的个人建议,是为了易于遵循和遵循。他们试图通过让库组件和编译器生成的代码完成所有资源管理来生成更简单的代码。

定义类中的(虚)析构函数,将禁止编译器提供的移动构造函数/赋值操作符,并且可能禁止编译器提供的复制构造函数/赋值操作符在C++的未来版本中。使用=default可以很容易地恢复它们,但看起来仍然有很多样板代码。最好的代码是您不必编写的代码,因为它不会出错(我知道这条规则还有例外)。

总结一下"不要将(虚拟)析构函数"定义为我的"零规则"的推论:

每当你在现代C++中设计一个多态(OO)类层次结构,并且想要/需要在堆上分配它的实例并通过基类指针访问它们时,使用EDCOX1(27)来实例化它们和EDCOX1,15来保持它们。这允许你保持"零规则"。

这并不意味着您必须分配堆中的所有多态对象。例如,定义一个以(Base&)为参数的函数,可以用一个局部Derived变量毫无问题地调用,并且对于Base的虚拟成员函数,它将表现出多态性。

在我看来,动态OO多态性在许多系统中被过度使用。当我们使用C++时,我们不应该像Java那样编程,除非我们遇到一个问题,其中堆分配对象的动态多态性是正确的解决方案。


我认为这与演示文稿中其他地方提到的"零规则"有关。

如果您只有自动成员变量(即对于原本是原始指针的成员使用shared_ptrunique_ptr),那么您不需要编写自己的复制或移动构造函数或赋值运算符——编译器提供的默认值将是最佳的。对于类内初始化,也不需要默认的构造函数。最后,您根本不需要编写一个析构函数,不管是虚拟的还是非虚拟的。


链接的纸张显示相关代码:

1
std::unique_ptr<Derived> { new Derived };

存储的删除程序是std::default_delete,它不要求Base::~Base是虚拟的。

<罢工>现在你可以把它移到一个unique_ptr,它也会移动std::default_delete,而不把它转换成std::default_delete。< /打击>