最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
C++中有两个函数负责对象拷贝,分别是拷贝构造函数和拷贝赋值运算符,我们可以统称为拷贝函数。若我们不声明自己的拷贝函数,则编译器会给你提供,若是声明了自己定义的拷贝函数,必须把所有成员变量拷贝。
void logCall(const std::string& funcName);
class Customer{
public:
...
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
private:
string name;
};
Customer::Customer(const Customer& rhs):name(rhs.name){ //使用初始化列表
logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs){
logCall("Customer copy assignment operator");
name = rhs.name; //拷贝数据
return *this; //返回*this
}
上述代码中自己声明的拷贝函数都很不错,直到另一个成员变量的出现
class Date {...};
class Customer{
public:
...
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
private:
string name;
Date lastTransaction; //新出现的成员变量
};
这时候上述的拷贝函数只是局部拷贝,它们的确复制了顾客的 name
,但没有赋值新出现的成员变量 lastTransaction
。大多数编译器不会因此而发出错误信息,即你的拷贝函数不完整,它不会告诉你。总的来说,你每添加一个成员变量,你就必须修改已有的拷贝函数(也需要修改 class
的所有搞糟函数以及任何非标准形式的 operator=
)。
一旦发生继承呢?假设 PriorityCustomer
类继承了 Customer
类,如下述代码:
class PriorityCustomer : public Customer{
public:
...
PriorityCustomer(const PriorityCustomer& rhs);
PriorityCustomer& operator=(const PriorityCustomer& rhs);
private;
int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
:priority(rhs.prority){ //使用初始化列表来构造该类的数据成员
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
logCall("PriorityCustomer copy assignment operator");
priority = rhs.priority; //拷贝该类的数据成员
return *this;
}
PriorityCustomer
的拷贝函数确实完整了复制 PriorityCustomer
中的专属成分,但它的基类 Customer
中的专属成分却未被复制。PriorityCustomer
的拷贝构造函数并没有指定实参传给其基类的构造函数(即它再它的初始化列表中没有提到 Customer
),因此 PriorityCustomer
中其基类的专属成分会被无参构造函数(必须有一个无参构造函数,不然无法通过编译,因为你定义了拷贝函数,所以编译器不会给你提供默认构造函数)初始化。
解决方法:
//拷贝构造函数:在初始化列表上调用其基类的拷贝函数
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
:Customer(rhs), //调用基类的拷贝构造函数
priority(rhs.prority){
logCall("PriorityCustomer copy constructor");
}
//拷贝赋值运算符:对基类成分进行赋值操作
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs){
logCall("PriorityCustomer copy assignment operator");
Customer::operator=(rhs); //对基类成分进行赋值操作
priority = rhs.priority;
return *this;
}
所以,当自己编写一个拷贝函数时,请确保:
- 复制所有本地成员变量
- 调用所有基类内的适当的拷贝函数
注意:在拷贝赋值运算符函数中调用拷贝构造函数是不合理的,反之,在拷贝构造函数中调用拷贝赋值运算符同样是无意义的。
Note:
- 拷贝函数应确保复制“对象内的所有成员变量”及“所有基类成分”
- 不要尝试以某个拷贝函数实现另一个拷贝函数。应将共同机能放进第三方函数中,并由两个拷贝函数共同调用