三/五法则
需要析构函数的类也需要拷贝和赋值操作
如果这个类需要一个析构函数,我们几乎可以肯定它也需要一个拷贝构造函数和一个拷贝赋值运算符。
需要拷贝操作的类也需要赋值操作,反之亦然
某些类所要完成的工作,只需要拷贝和赋值操作,不需要析构函数。一个类需要一个拷贝赋值运算符,几乎可以肯定它也需要一个拷贝构造函数。
使用=default
我们可以通过将拷贝控制成员定义为=default来显式地要求编译器生成合成的版本:
class Sales_data {
public:
//拷贝控制成员;使用default
Sales_data()=default;
Sales_data(constr Sales_data&)=default;
Sales_data& operator=(const Sales_data &);
~Sales_data()=default;
//其他成员的定义,如前
};
Sales_data& Sales_data::operator=(const Sales_data&)=default;
当我们在类内用=default修饰成员的声明时,合成的函数将隐式地声明为内联的,如果我们不希望合成的成员是内联函数,应该只对类外定义使用=default。
阻止拷贝
有些情况下,我们想要阻止或者改变类的拷贝。
定义删除的函数
我们可以通过将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝。删除函数是这样一种函数:我们虽然声明了它们,但不能以任何方式使用它们。在函数的参数列表后面加上=delete来指出我们希望将它定义为删除的:
struct NoCopy {
NoCopy()=default; //使用合成的默认构造函数
NoCopy(const NoCopy&)=delete; //阻止拷贝
//....
};
与=default不同,=delete必须出现在函数第一次声明的时候,我们可以对任何函数指定=delete,我们只能对编译器可以合成的默认构造函数或拷贝控制成员使用=default。
析构函数不能是删除的成员
我们不能删除析构函数,如果析构函数被删除,就无法销毁此类型的对象了,对于一个删除了析构函数的类型,编译器将不允许定义该类型的变量或创建该类的临时对象。对于删除了析构函数的类型,我们可以动态分配这种类型的对象,但是不能释放这些对象:
struct NoDtor {
NoDtor()=default; //使用合成默认构造函数
~NoDtor()=delete; //我们不能销毁NoDtor类型的对象
};
NoDtor nd; //错误:NoDtor的析构函数是删除的
NoDtor *p=new NoDtor(); //正确:但我们不能delete p
delete p; //错误:NoDtor的析构函数是删除的
合成的拷贝控制成员可能是删除的
如果一个类为定义构造函数,编译器会为其合成一个默认构造函数,对某些类来说,编译器将这些合成的成员定义为删除的函数。如果一个类有数据成员不能默认构造、拷贝、复制或销毁,则对应的成员函数将被定义为删除的。
private拷贝控制
在新标准发布之前,类通过将其拷贝构造函数和拷贝赋值运算符声明为private的来阻止拷贝。