复制控制
一、复制控制
类能控制复制,赋值,撤销该类的对象时的动作,分别通过下面的成员函数:
1. 复制构造函数:具有单个形参,该形参是对该类类型的引用(通常用 const修饰);
2. 赋值操作符
3. 析构函数:不管有没显示定义,编译器都自动执行类中非static数据成员的析构函数
这三个函数就成为复制控制。
二.为什么要研究复制控制
如果没有显示地定义复制构造函数和赋值操作符,编译器会为我们定义。
但是编译器合成的复制控制函数只做必需的工作,某些类如果依赖于默认的定义会导致错误,例如类具有指针成员。
难点: 识别何时需要覆盖默认版本,定义自己的复制构造函数
所以有时候要自定义复制构造函数,定义复制构造函数跟构造函数是一样的,难点是能认识到需要定义复制构造函数。
(具体请看第五点:”什么时候要自己的复制构造函数”)
复制控制函数
三.复制构造函数会几时用到?
首先:区分构造函数和复制构造函数
1.根据另一个同类型的对象显式或者隐式地初始化一个对象
区别下面:
(1)下面的构造函数和复制构造函数仅在低级别优化上存在差异
String str3(10,"a"); // 调用构造函数
String str4 ; // 调用构造函数
String str1= "hello"; // 调用复制构造函数
String str2 = string(); // 调用复制构造函数
(2)对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别:
Ifstream file1("filename"); //OK,调用构造函数
Ifstream file2 = "filename" ; //ERROR, 不能复制IO类型的对象
//(复制构造函数是private 的)(原因见第七项)
Sales_item item = string("hello"); //取决于构造函数是不是explicit,如果构造函数 是显式的,则初始化失败,如果构造函数是隐式的,则初始化成功。
2.一个对象作为实参传给一个函数,或者作为函数的返回值
例如: string fun(const stirng& A, const string B);
返回值和 第二个参数B隐式地调用复制构造函数;第一个参数A,是const引用,不能复制。
3.初始化顺序容器中的元素
当用表示容量的单个形参来初始化容器的时候,用到了默认构造函数和复制构造函数。
Vector<string> svec(5);
先用string默认构造函数创建一个临时值来初始化svec,然后是使用复制构造函数将临时值复制到svec的每一个元素。
4.根据“元素初始化列表"初始化数组元素
(1)如果没有为类类型数组提供元素初始化式,是调用默认构造函数初始化每个元素,
Sale_item ClassArray[];
(2)如果用花括号扩展的数组初始化列表来提供显式元素初始化,则是用复制构造函数.
根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应的元素。
Sale_item ClassArray2[] = { string("ab"), //直接指定一个值,调用单实参构造函数
string("cd"),
Sale_item(), // 使用完整的构造函数语法(0或者多个实参)
};
备注:可以直接指定一个值,调用元素类型的单实参构造函数,如前两个元素的初始化;
如果不指定实参或者指定多个实参,就需要使用完整的构造函数语法,如最后一个元素的初始化
四.编译器合成的复制构造函数
前面提到,如果我们没有定义,编译器会默认合成一个复制构造函数,该函数会对该对象的每一个非static成员依次复制到正创建的对象。
其中,不同类型的复制如下:
1.直接复制内置类型成员的值(不是指针)
2.类型成员使用该类的复制构造函数
3.数组复制数组的每一个元素
等价于: 一个构造函数,每个数据成员在构造函数初始化列表中进行初始化;
例如 有三个数据成员的类的合成构造函数:
Sales_item::Sales_item(const Sales_item &orig)
A(orig.A),
B(orig.B),
C(orig.C)
{//Empty}
五.什么时候要自己定义复制构造函数(重点)
有些类必须对复制对象时发生的事情进行控制,例如
1、类中有数据成员是指针或者有成员在构造函数中分配其他资源
2、在创建对象时必须做一些特定工作
以上的情况都必须定义复制构造函数。
六、怎么定义复制构造函数(同构造函数一样)
同类同名,没有返回值,可以使用构造函数初始化列表初始化新创建对象的成员,可以在函数体中任何其他必要的工作。
七、如何禁止复制
例如:iostream类就不允许复制
1.如果不自定义复制构造函数,编译器也会自动合成一个,无法禁止复制
2.为了防止复制,类必须显式地声明其复制构造函数为 private(其友元和成员依然可以复制)
3.如果要连友元和成员的复制也禁止,就可以声明一个private的复制构造函数但不对它进行定义。(如果复制类对象会提示编译错误,如果成员和友元尝试复制就会导致链接错误)
八、赋值操作符
1.如果没有定义自己的赋值操作符,编译器也会自动合成一个(类似于合成的复制构造函数,进行各个成员的赋值)
2.如果类自定义自己的复制操作符,那么一般类也需要自定义赋值操作符(具体的请看后面的例子)
九、析构函数
1、 析构函数通常用于释放在构造函数或者在对象生命期内获取的资源。析构函数可以执行任意操作,一般是在类对象使用完毕之后要执行的动作。
三法则: 如果类需要析构函数,则它也需要复制操作符和复制构造函数。
2、合成的析构函数按创建的逆序撤销每个非static 成员。(并不删除指针成员所指向的对象),无论有没创建自定义函数,编译器都会合成析构函数
3、注意:即便自定义了自己的析构函数,编译器在运行自定义析构函数之后,还会运行合成析构函数。