1、复制构造和复制赋值是两种不同的操作。它们一般被放到一起,同时出现,并且必须兼容:
class Impl;
class Handle {
public:
//.....
Handle( const Handle & ); //复制构造函数
Handle &operator = ( const Handle & ); //复制赋值操作符
void swap( Handle & );
//.....
private:
Imple *impl_ ; //指向Handle的实现
};
2、对于一个类X而言,复制构造函数应该被声明为X( const X & ),而复制赋值操作符则应该被声明为X &operator = ( const X & )。
典型的非成员形式的swap实现是很直观的:
template <typename T>
void swap( T &a, T &b) {
T temp(a); //调用T的复制构造函数
a = b; //调用T的复制赋值操作符
b = temp; //调用T的复制赋值操作符
}
如果T的实现短小简单,这种方式很好。若是T是一个庞大的类,就会有不小的开销。对于Handle这样的类,一般我们采用交换指向各自实现的指针的方式:
inline void Handle::swap( Handle &that )
{ std::swap( impl_, that.impl_ ); }
3、对于复制赋值的这个实现来说,微妙之处在于复制构造的行为必须和复制赋值的行为“兼容”。尽管它们是不同的操作,然而此处存在一个影响深远的惯用假定,就是它们产生的结果不应该有区别:
也就是说,不管写成:
Handle a = ......
Handle b = ......
b = a; //将a赋给b
还是写成:
Handle a = ......
Handle b(a); //用a来初始化b
b的结果值和将来的行为都应该没有差别,不管它是通过赋值还是通过初始化而得到那个值的。
4、当使用标准的容器时,这种兼容性尤其重要,因为它们的实现常常用复制构造来代替复制赋值,当然也就期望两种操作产生一致的结果。
一个或许更常见的复制赋值实现具有如下结构:
Handle &Handle::operator = ( const Handle &that ) {
if( this != &that ) {
//进行赋值....
}
return *this;
}
这种对自身赋值所执行的检查往往是出于正确性的考虑(有时出于效率方面)。更确切地说为了确保赋值表达式的左操作数(this)和右操作数(例如that)具有不同的地址。