当定义一个类时,我们显式或隐式指定在此类型的对象执行拷贝,移动,赋值,销毁时做什么,通过拷贝构造函数,拷贝赋值运算符,移动构造函数,移动赋值运算符和析构函数。
拷贝赋值与销毁
如果构造函数的第一个参数是自身类类型的引用,并且其他参数都有默认值,则此构造函数是拷贝构造函数。第一个参数一定是引用类型,并且一般为const类型。
直接初始化对象时,我们使用最匹配参数的构造函数初始化对象,拷贝初始化时(赋值的形式)会使用拷贝构造函数初始化对象。通常拷贝初始化由拷贝构造函数来完成,但如果一个类有移动构造函数,则使用移动构造函数执行拷贝初始化。用等号(=)定义变量时,将一个对象作为实参传递给一个非引用类型的形参,从函数返回一个非引用类型的对象,用花括号初始化数组元素或聚合类中的成员时,这些情况会执行拷贝初始化。
赋值运算符就是一个名为operator=的函数,赋值运算符返回一个指向其左侧运算对象的引用。如果未定义自己的拷贝赋值运算符,编译器会合成一个拷贝赋值运算符。
析构函数与构造函数执行相反的操作,析构函数没有返回类型也不接受任何参数。析构函数不能重载,对于一个给定类,只能有一个析构函数。析构函数首先执行函数体,然后销毁成员,析构是隐式执行的,销毁内置指针类型的成员时不会delete所指向的对象。当指向一个对象的引用或指针离开作用域时,析构函数不会执行。
如果一个类需要一个析构函数,几乎可以肯定该类也需要一个拷贝构造函数和拷贝赋值运算符。需要拷贝操作的类也需要拷贝赋值操作,需要拷贝赋值运算符的类也一定需要拷贝构造函数,但不一定需要析构函数。注意这里所说的需要是只自定义。
可以通过将拷贝控制成员声明为=default来显示要求编译器生成合成的版本。
通过在函数后面添加=delete来定义删除的函数,声明删除的函数将阻止对该函数的使用。析构函数不能是删除的,否则将无法销毁该对象,可以动态分配一个有删除的析构函数的对象,但是不能delete该对象。
合成的拷贝控制成员可能是删除的,如果类的某个成员含有删除的拷贝控制成员或析构函数,则该类的合成拷贝控制也将是删除的。
对于具有引用成员或const成员的类型,编译器不会为其合成构造函数,如果一个类有const成员,则不能使用合成的拷贝赋值运算符。对于有引用成员的类,合成拷贝赋值运算符被定义为删除的。本质上,当不能销毁拷贝赋值类的成员时,合成的拷贝控制成员就被定义为删除的。
声明但不定义一个成员函数是合法的,访问一个未定义的成员将导致链接时错误。
拷贝控制和资源管理
通常管理类外资源的类需要定义拷贝控制成员。
对于赋值运算符来说,即使将一个对象赋予它自身也应该能正常工作,好的方法是在销毁左侧运算对象之前拷贝右侧运算对象。赋值运算符组合了析构函数和拷贝构造函数的功能。
交换操作
如果一个类定义了自己的swap函数,算法将使用自定义函数,否则使用标准库swap版本。swap并不是必要的,但是使用了资源的类定义swap将是一种重要的优化手段。