C++初级读本第5艾德- Stanley:具有空析构函数的基类能使其成为虚的吗?

lawou6xi  于 2022-12-20  发布在  其他
关注(0)|答案(2)|浏览(86)

我正在阅读上面这本书的摘录(第15章-面向对象编程第15. 7. 1节虚拟析构函数)。
基类的析构函数是经验法则的一个重要例外,如果一个类需要一个析构函数,它也需要复制和赋值(参见13.1.4节),基类几乎总是需要一个析构函数,这样它就可以使析构函数虚化。

如果一个基类有一个空析构函数来使它成为虚的,那么这个类有一个析构函数的事实并不表明也需要赋值运算符或复制构造函数。

我不明白其中提到的“如果一个基类有一个空析构函数,这样我就可以使它成为虚的”,我认为使它成为虚的唯一方法是使用关键字“virtual”。
任何解释都是伟大的!

2mbi3lxu

2mbi3lxu1#

这里的作者认为空的virtual析构函数是一个仍然是用户声明的析构函数(不是因为基类析构函数而隐式声明为virtual的析构函数),并且要么定义为空的函数体,要么默认为:

virtual ~MyClass() = default;

virtual ~MyClass() {}

不可能强制隐式声明的析构函数为virtual(如果还没有具有virtual析构函数的基类),因此这两种方法都是使析构函数virtual具有与隐式定义的(非virtual),这就是为什么作者说“* 为了使它成为virtual *"。
引用的意思是说,类中包含这两个操作符并不表示该类需要定义自己的复制赋值运算符或复制构造函数,析构函数仍然与隐式定义的析构函数完全相同,因此隐式定义的复制操作在语义上仍然是正确的。
一个非空析构函数,例如一个对类执行一些非平凡工作的析构函数(不仅仅是简单的日志记录等),通常表明隐式定义的复制操作在语义上对类的预期行为是不正确的,这就是为什么有提到的三分之一规则说,在这种情况下,复制操作也应该手动定义,以避免非预期的语义。
然而,在移动语义方面,空的virtual析构函数仍然存在问题。以任何方式声明 any 析构函数都将禁止移动构造函数和移动赋值运算符的隐式声明。
因此,如果你的类有一个std::vector成员,它将变得低效,因为它永远不能被移动,只能被复制。通常一个有virtual析构函数的类是多态使用的,而不是被复制/移动,但情况并不总是如此。所以在这种情况下,仍然显式默认所有的移动和复制操作(与五分之一规则一致)是有意义的:

virtual ~MyClass() = default;
MyClass(const MyClass&) = default;
MyClass(MyClass&&) = default;
MyClass& operator=(const MyClass&) = default;
MyClass& operator=(MyClass&&) = default;

或者(可能更有可能),假设类可能只打算多态地使用,显式地delete所有这些,以使对象切片不可能无意中发生(参见C++核心指南C.67):

virtual ~MyClass() = default;
MyClass(const MyClass&) = delete;
MyClass(MyClass&&) = delete;
MyClass& operator=(const MyClass&) = delete;
MyClass& operator=(MyClass&&) = delete;
qv7cva1a

qv7cva1a2#

首先,这意味着,对于基类Base,您可能需要一个virtual析构函数:

class Base {
public:
    virtual ~Base() {} // note the empty body
};

这是为了通过基指针删除后代调用正确的ctor并正确地释放内存。注意这里不需要复制/赋值;在这一点上,我们甚至不知道子类是应该有数据成员,还是仅仅实现一个没有任何数据的接口。
然而,只有当你使用裸指针和一些智能指针(例如std::unique_ptr<>)时,这才是正确的。当你使用std::shared_ptr<>时,你的析构函数会自动保存,所以你不需要virtual析构函数在销毁std::shared_ptr<Base>之前将std::shared_ptr<Descendant>保存在std::shared_ptr<Base>中。

相关问题