条款 17 - 理解特种成员函数的生成机制
“特种成员函数”指的是那些C++编译器在特定情况下会自动生成的成员函数。在C++11之后, 特种成员函数指的是默认构造函数, 析构函数, 拷贝构造函数, 拷贝赋值运算符, 移动构造函数, 移动赋值运算符。一般来说, 这些函数仅在代码中需要它们,且程序员没有显式声明它们时才会被自动生成。同时, 这些函数的自动生成存在一些机制, 下面我们来详细了解一下。
移动操作的生成规则
编译器仅在同时满足以下所有三个条件时,才会为类生成移动操作(移动构造函数和移动赋值运算符) :
- 类中没有用户声明的拷贝操作(拷贝构造函数或拷贝赋值运算符)。
- 类中没有用户声明的移动操作(移动构造函数或移动赋值运算符)。
- 类中没有用户声明的析构函数。
针对上述内容, 解释如下:
首先, 如果程序员为一个类编写了拷贝操作,这表明默认的、按成员拷贝的方式不适用于该类。编译器据此推断,默认的、按成员移动的方式很可能也不适用,因此不再自动生成移动操作 。
而如果程序员编写了析构函数,这通常意味着该类在进行某种需要手动清理的资源管理,这正是C++98“三法则”(Rule of Three)的核心思想。在这种情况下,编译器假定默认的成员移动操作是不正确的,因此也会禁止生成移动操作。
三法则是指, 如果你声明了拷贝构造函数、拷贝赋值运算符,或析构函数中的任何一个,你就得同时声明所有三个函数。
拷贝操作的生成规则
如果用户为一个类声明了移动操作(移动构造函数或移动赋值运算符),编译器会将拷贝操作(拷贝构造函数和拷贝赋值运算符)删除(即 delete)。
其逻辑是,如果一个类需要自定义的移动逻辑,那么简单的按成员拷贝很可能是不正确的。为了防止意外调用了行为不正确的拷贝函数,编译器选择直接将其删除,而不是简单地不生成。
但是, 为了兼容部分C++98代码, 如果用户声明了析构函数或复制构造函数,编译器仍然会自动生成复制赋值运算符
强制生成函数:= default
如果因为上述规则,某个特种成员函数没有被自动生成(例如,因为声明了析构函数而导致移动操作被抑制),但我们确信编译器生成的默认实现正是所需要的,可以使用 = default 来显式地要求编译器生成默认版本 。
1 | class Base { |
其他规则
默认构造函数仅当类中不包含用户声明的构造函数时才生成。
另外, 一个需要特别注意的规则是:类中的成员函数模板(例如一个接受任意类型的构造函数模板)永远不会抑制编译器生成任何特种成员函数