类通过5种特殊的成员函数:拷贝构造函数(copy constructor)、拷贝赋值运算符(copy-assignment operator)、移动构造函数(move constructor)、移动赋值运算符(move-assignment)和析构函数(destructor),来完成对象的拷贝、移动、赋值和销毁。
class Foo(){
public:
Foo();
~Foo();
Foo(const Foo&); //拷贝构造函数
Foo& operator=(const Foo&); //拷贝赋值赋值运算符
}
拷贝构造函数
- 即使定义了其他构造函数,编译器也会合成一个拷贝构造函数。
- 合成拷贝构造函数将逐个拷贝非static成员变量。
- 对类成员,使用其拷贝构造函数、
- 对数组,逐个拷贝(元素类型的拷贝构造函数)
- 拷贝初始化通常通过 拷贝构造函数 来完成
Foo a;
Foo b = a; //拷贝初始化
String s = "a"; //拷贝初始化
- 编译器有时可以跳过拷贝/移动构造函数
string s = "a"; //拷贝初始化
//可执行为
string s("a"); //直接初始化
拷贝赋值运算符
- 未定义则生成合成拷贝复制运算符,行为类似拷贝构造函数
- 定义拷贝复制运算符时需要注意:如果一个对象赋予它自身时,必须能正常工作
- 使用swap的拷贝赋值运算符(拷贝并交换)。自动处理了自赋值情况其是异常安全的。
Foo& operator=(Foo rhs){ //注意rhs不是引用!!
swap(*this, rhs);
return *this;
}
//结束时,结束时原来该对象中的资源,被rhs释放
三/五法则
- 需要析构函数的类也需要拷贝和赋值操作(需要拷贝/赋值操作 不一定 需要析构函数)
- 需要拷贝操作的类也需要赋值操作,反之亦然
- 一般来说一个类定义了任何一个拷贝操作,它就应该定义所有五个操作。
使用=delete来阻止拷贝
- 不会生成合成的拷贝构造函数/拷贝赋值运算符
Foo(const Foo&) = delete; //阻止拷贝
Foo& operator(const Foo&) = delete; //阻止赋值
- 删除了 析构函数的类,不能定义该变量或创建其临时变量。但可以动态分配,但无法释放。
- 如果一个类的数据成员不能默认构造/拷贝/赋值/销毁,则对应的合成的成员函数将被定义为删除的。
- 某个成员的析构函数是删除的不可访问的(如private)/类内有一个引用成员/类内有一个const成员且没有类内初始化,则该该类合成的构造函数被删除
swap
标准库的swap,需要一次拷贝和两次赋值。对于有类外资源的对象来说,更好的方式是交换指针,而不是拷贝。 为了优化,需自己定义swap。