条款 17 - 理解特种成员函数的生成机制

ZaynPei Lv6

“特种成员函数”指的是那些C++编译器在特定情况下会自动生成的成员函数。在C++11之后, 特种成员函数指的是默认构造函数, 析构函数, 拷贝构造函数, 拷贝赋值运算符, 移动构造函数, 移动赋值运算符。一般来说, 这些函数仅在代码中需要它们,且程序员没有显式声明它们时才会被自动生成。同时, 这些函数的自动生成存在一些机制, 下面我们来详细了解一下。

移动操作的生成规则

编译器仅在同时满足以下所有三个条件时,才会为类生成移动操作(移动构造函数和移动赋值运算符) :

  • 类中没有用户声明的拷贝操作(拷贝构造函数或拷贝赋值运算符)。
  • 类中没有用户声明的移动操作(移动构造函数或移动赋值运算符)。
  • 类中没有用户声明的析构函数

针对上述内容, 解释如下:

首先, 如果程序员为一个类编写了拷贝操作,这表明默认的、按成员拷贝的方式不适用于该类。编译器据此推断,默认的、按成员移动的方式很可能也不适用,因此不再自动生成移动操作 。

而如果程序员编写了析构函数,这通常意味着该类在进行某种需要手动清理的资源管理,这正是C++98“三法则”(Rule of Three)的核心思想。在这种情况下,编译器假定默认的成员移动操作是不正确的,因此也会禁止生成移动操作。

三法则是指, 如果你声明了拷贝构造函数、拷贝赋值运算符,或析构函数中的任何一个,你就得同时声明所有三个函数。

拷贝操作的生成规则

如果用户为一个类声明了移动操作(移动构造函数或移动赋值运算符),编译器会将拷贝操作(拷贝构造函数和拷贝赋值运算符)删除(即 delete)。

其逻辑是,如果一个类需要自定义的移动逻辑,那么简单的按成员拷贝很可能是不正确的。为了防止意外调用了行为不正确的拷贝函数,编译器选择直接将其删除,而不是简单地不生成。

但是, 为了兼容部分C++98代码, 如果用户声明了析构函数或复制构造函数,编译器仍然会自动生成复制赋值运算符

强制生成函数:= default

如果因为上述规则,某个特种成员函数没有被自动生成(例如,因为声明了析构函数而导致移动操作被抑制),但我们确信编译器生成的默认实现正是所需要的,可以使用 = default 来显式地要求编译器生成默认版本 。

1
2
3
4
5
6
7
8
9
10
11
12
class Base {
public:
virtual ~Base() = default; // 一旦用户声明了析构函数,移动操作的自动生成就被抑制了

// 因此还需要制编译器生成被抑制的移动操作
Base(Base&&) = default;
Base& operator=(Base&&) = default;

// 为了可移动,最好也把复制操作加上
Base(const Base&) = default;
Base& operator=(const Base&) = default;
};

其他规则

默认构造函数仅当类中不包含用户声明的构造函数时才生成。

另外, 一个需要特别注意的规则是:类中的成员函数模板(例如一个接受任意类型的构造函数模板)永远不会抑制编译器生成任何特种成员函数

On this page
条款 17 - 理解特种成员函数的生成机制