设计良好的面向对象系统会将对象的内部封装起来,只留两个函数负责对象拷贝(复制),就是copy构造函数和赋值操作符,我们称之为copying函数。前面我说了,编译器会在必要时候为我们的类生成copying函数,并说明这些“编译器生成版”的行为:将被拷贝对象的所有成员都拷贝一份。
如果你声明自己的copying函数,意思就是告诉编译器你不喜欢缺省实现中的某些行为。编译器像是被冒犯似的,会以一种奇怪的方式回敬你:当你的实现代码几乎必然出错时不告诉你。
考虑一个类用来表现顾客,其中手工写出copying函数,使得外界对它们的调用会被志记下来:
void logCall(const std :: string& funcName); //制造一个log entry
class Customer
{
public:
...
Customer(cosnt Customer& rhs);
Customer& operator = (const Customer& rhs);
...
~Customer();
private:
std::string name;
};
Customer::Customer(const Customer& rhs):name(rhs.name)
{
logCall("Customer copy constructor");
}
Customer& Customer::operator=(const Customer& rhs)
{
logCall("Customer copy constructor");
name = rhs.name;
return *this;
}
这里每一件事看起来都很顺利,实际也如此,直到令一个成员变量加入:
class Date{...}; //日期
class Customer
{
public:
... //同前
private:
std::string name;
Date lastTransaction;
};
这时候已经有的copying函数执行的是局部拷贝:它们的确复制了顾客的name,但没有复制新添加的lastTransaction。大多数编译器对此不会有报错,结论就是:你既然自己写了copying函数,就得自己负责,编译器是不会管的了。
一旦发生了继承,可能会造成一个潜藏危机:
class PriorityCustomer:public Customer
{
public:
...
PriorityCustomer(const PriorityCustomer& rhs);
PriorityCustomer& operator = (const PriorityCustomer& rhs);
...
private:
int priority;
}
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):
priority(rhs.priority)
{
logCall("PriorityCustomer copy constructor");
}
PriorityCustomer& PriorityCustomer::operator = (const PriorityCustomer& rhs)
{
logCall("PriorityCustomer copy assignment operator");
priority = rhs.priority;
return *this;
}
看起来该类的copying函数好想复制了每一样东西,但再看一眼,我们忘了基类中的name和lastTransaction,所以要记得在copying函数上补上,我这里就不过多赘述了。
当你编写一个copying函数,请确保1、复制所有local成员变量,2、调用基类内适当的copying函数。
总结:
1、copying函数应该确保复制“对象内的所有成员变量“及“所有基类成分“;
2、不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。