【Effection C++】读书笔记 Part2 构造/析构/赋值运算
条款05:了解C++默认编写并调用哪些函数
编译器可以暗自为class创建default构造函数,copy构造函数,copy assignment操作符,以及析构函数。并且只有当这些函数真正被调用的时候,它们才会被编译器创建出来。
要注意拷贝运算符只有在合适的条件下才会被创建出俩,否则编译器可能会生成删除的拷贝运算符。
- 如果一个类的某个成员的析构函数是删除的或者是不可访问的,则类的合成析构函数被定义为删除的。
- 如果一个类的某个成员的拷贝构造函数是删除的或者是不可访问的。则类的合成拷贝构造函数被定义为删除的。如果类的某个成员是删除的或者不可访问的,则类的拷贝构造函数也被定义为删除的。
- 如果类的某个成员的拷贝赋值运算符是删除的或者是不可访问的,或者类有一个const成员或者引用成员,则类的合成拷贝赋值运算符被定义为删除的。
- 如果一个类的某个成员的析构函数是删除的或者是不可访问的,或者是类有一个引用成员/const成员,没有进行类内初始化(C++11),或者是其类内有成员无默认构造函数,那么该类的默认构造函数被定义为删除的。
合成的移动构造函数和移动赋值运算符
除此之外,在合适的条件下(只有当一个类没有定义子集任何版本的拷贝控制成员,并且类中的每个非static数据成员都可以移动时),编译器才会合成移动构造函数和移动赋值运算符。
如果一个类定义了自己的拷贝构造函数,拷贝赋值函数,析构函数,那么编译器就不会为其合成移动构造函数和移动赋值函数。即有的类可能根本就不存在移动构造函数和移动赋值函数,如果需要调用此函数时候,会匹配到拷贝构造函数和拷贝赋值函数。
只有当一个类没有定义任何自己版本的拷贝控制成员,也没有定义自己的析构函数,且类的每个非static数据成员都可以移动时,编译器才为其合成移动构造函数和移动赋值运算符。
类的移动操作不会隐式定义为删除的函数(如果不能合成,就不存在对应的移动操作),但是如果我们显示要求编译器生成=default的移动操作,且编译器不能够移动所有成员,则编译器会将移动操作定义为删除的函数。
其中,编译器将移动构造函数和移动赋值运算符定义为删除的条件如下:
- 一个类成员定义了自己的拷贝构造函数且未定义移动构造函数,或类成员未定义自己版本的拷贝构造函数且编译器不能为其合成移动构造函数,则此类的移动构造函数定义为删除的。移动赋值运算符类似。
2.类中有某个成员的移动构造函数或移动赋值运算符被定义为删除的或者是不可访问的,则类的移动构造函数或者移动赋值运算符被定义为删除的。
类的析构函数被定义为删除的或者是不可访问的,则类的移动构造函数被定义为删除的。
类中有成员是const或引用的,则类的移动赋值运算符被定义为删除的。
并且要注意,如果一个类定义了自己的移动构造函数/移动赋值运算符,那么合成的拷贝构造函数和拷贝赋值运算符就被定义为删除的。
条款06:若不想使用编译器自动生成的函数,就明确拒绝
如果不希望编译器自动生成函数,可以采用如下的做法:
- 在C++11标准之前,可以将相应的成员函数声明为private并且不予实现。这样子对于使用该函数的客户对象而言,编译器会报错(private权限控制),对于类的成员或者友元函数,会导致链接器报错(因为无具体实现)。
- 在C++11标准之前,还可以定义一个基类,将其基类的对应成员函数定义为私有的,然后新创建的类继承此基类,编译器将会自动拒绝会这样的类生成相应的默认函数。
- 在C++11标准之后,可以直接将对应的函数设置为=delete。
class A
{
public:
...
A() = delete; //默认构造函数被设置为delete
A(const A&) = delete; //拷贝构造函数被设置为delete
...
};