前言
</br>
设计良好的OO System一般只留有两个函数负责对象拷贝,那就是copy构造函数和copy assignment操作符,暂称它们为copying函数。正如前文所说,编译器会在必要的时候为我们的classes创建copying函数,其行为是:将被copy对象的所有成员变量都做一份拷贝。
手动处理拷贝可能存在的风险
</br>
如果我们自己声明和实现了copying函数,但却并没有完全拷贝成员变量,造成了partial copy,此时编译器根本不会报错,所以需要仔细地排查:1
2
3
4
5
6
7
8class Widget{
public:
Widget(const Widget& rhs)
:name(rhs.name) {};//忘记了copy age 不会报错
private:
string name;
int age;
}
由于这种特性,每一次新的成员变量的加入,copying函数,构造函数,以及任何重载的operator=都必须要做出修改。你就不用指望编译器会提醒你了。
继承体系下的partial copy
问题实例
假设我们的继承体系中存在着Customer与PCustomer两个类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Customer{
public:
...
private:
string name;
Date lastTransaction;
}
class PCustomer:public Customer{
public:
...
PCustomer(const PCustomer &rhs);
PCustomer& operator=(const PCustomer &rhs);
...
private:
int priority;
};
PCustomer::PCustomer(const PCustomer &rhs)
:priority(rhs.priority) {}
看起来确实执行了copy,但实际上dc内部的bc成分并未被copy。在这种情况下,bc成分将会被默认初始化。
如果是operator=,则仅仅复制了dc成分,bc成分保持不变。
解决方案
在任何时候我们一旦承担起为derived class编写copying函数的责任,必须要注意一定要记得复制base class的成员变量 ,一般情况下它们是private,所以我们调用base class的copying函数来完成它们。针对上面的实例,两个copying函数的正确写法如下所示:1
2
3
4
5
6
7
8PCustomer::PCustomer(const PCustomer &rhs)
:Customer(rhs),priority(rhs.priority) {}
PCustomer&
PCustomer::operator=(const PCustomer &rhs){
Customer::operator=(rhs);
priority=rhs.priority;
return *this;
}
copy构造函数与copy assignment运算符之间的关系
</br>
一般来说,copying 函数往往有近似的实现本体,这可能会让我们想到在一个中调用另一个。但这并不可取。
令copy assignment调用copy construct函数并不科学,原因很简单,你在试图构造一个已经存在的对象。
令copy construct函数调用copy assignment同样毫无意义。构造函数用来初始化新对象,而assignment操作符只施行于已经被初始化的对象。对还没构造好的对象赋值完全毫无意义。
如果它们两个之间真的存在相近的代码,那应该编写一个private的init函数,给他们两个调用,这样就完美消除了代码重复的问题。
总结
- copying函数应该确保复制对象内所有成员,以及所有base class成分(调用bc对应的copying函数)
- 不要尝试以某个copying函数实现另一个copying 函数。应该新建一个private init函数给二者调用。