使用Copy Constructor的目的:
用一个已经存在的对象去初始化一个新创建的对象
eg: Book book3(book1);
也等同于:Book book3 = book1;
关于Copy Constructor很重要的一点:
和默认构造函数、默认析构函数一样,拷贝构造函数也有C++自动创建的 默认拷贝构造函数(Default copy constructor)。但是,只要涉及到指针、动态内存分配,就必须要自己定义copy constructor了【这个注意点和析构函数一样】,否则会报错。
比如下面这个class:
class Book {
public:
string title;
float * rates;
int ratesCount;
Book(string inputTitle) {
title = inputTitle;
ratesCount = 2;
rates = new float[ratesCount];
rates[0] = 4; //hard code the array just for examples
rates[1] = 5;
}
~Book(){
delete[] rates;
rates = nullptr;
}
};
//main中定义了一个对象,并通过拷贝构造函数给另一个对象初始化
Book book1("Millionaire Fastlane");
Book book3(book1);
创建Copy Constructor需要注意的几点:
- 是class中的public member function
- 无返回值
- 和class名一样
- 只接受一个参数,并且参数也是该class类型的
- 参数是通过call-by-reference传递的,并且是const的
关于第5点的详细解释:
需要call-by-reference的原因:值传递会产生逻辑错误。如果是通过值传递(call-by-value)的,那么意味着是将原来的对象拷贝了一份给拷贝构造函数,这里就发生了一个逻辑错误,因为如果通过值传递拷贝对象给函数,那么首先就先得有一个拷贝构造函数了,而我们现在就是在自行创建copy constructor。因此需要通过call-by-reference(&)来给copy constructor传参。
需要加const的原因:由于是call-by-reference了,所以函数就可能会改变原对象的成员属性值,因此要加上const,确保传过来的对象在函数中是一个constant,函数不会改变它的属性。
根据以上的注意事项,写出了下面的copy constructor,但还是会报错:
Book(const Book& original) {
title = original.title;
rates = original.rates; // 这里有Error
ratesCount = original.ratesCount;
}
报错原因:由指针引起,在调用析构函数时产生错误。
rates是一个指针,它指向的动态内存(rates[0]和rates[1]所占的内存),在book1调用析构函数时,被释放了。而上面错误写法的拷贝构造函数中,是把book1的指针变量直接赋给book3,所以book3的rates指针也指向同一块内存(rates[0]和rates[1]在被释放之前所占的内存)。在book3调用析构函数时,delete的是一块已经被释放的内存,因此报错。
涉及到拷贝动态内存的解决方法:
在拷贝构造函数中,分配一块新的动态内存,将需要拷贝的数组的值通过遍历循环,拷贝给新的内存。
Book(const Book& original) {
title = original.title;
ratesCount = original.ratesCount;
rates = new float[ratesCount];
for (int i = 0; i < ratesCount; i++) {
rates[i] = original.rates[i];
}
}
需要调用Copy Constructor的情况:
1. 用一个已经存在的对象去初始化一个新创建的对象
eg: Book book3(book1);
或者:Book book3 = book1; // 【注意与assignment operator的区分】
copy constructor与assignment operator的区分:
用已存在对象初始化一个新创建的对象,是调用了拷贝构造函数。
用已存在对象去给另一个已被初始化过的值重新赋值,才是assignment operator,比如:book3 = book2;
2. 当编译器创建临时对象时
① 函数通过对象的值传参(call-by-value),此时compiler会创建一个对象的副本,调用拷贝构造函数
② 函数返回值为对象(且返回的也是对象的value,而不是address),此时compiler也会创建一个临时对象(即对象的副本),调用拷贝构造函数。