设为首页 加入收藏

TOP

条款13 复制操作
2013-10-07 13:16:23 来源: 作者: 【 】 浏览:59
Tags:条款 复制 操作

条款13 复制操作

复制构造(copy construction)和复制赋值(copy assignment)是两种不同的操作。从技术角度来说,它们之间没有丝毫的关联,但从“社会”角度来说,它们一般被放到一起,同时出现,并且必须兼容:

  1. class Impl;  
  2. class Handle {  
  3.   public:  
  4.     //...  
  5.     Handle( const Handle & ); // 复制构造函数  
  6.     Handle &operator =( const Handle & ); // 复制赋值操作符  
  7.     void swap( Handle & );  
  8.     //...  
  9.   private:  
  10.     Impl *impl_; // 指向Handle的实现  
  11. }; 

复制操作的影响是如此深远,以致于遵守惯例变得异常重要。这两个操作总是被成对地声明,具有如上所示的签名(另请参考“auto_ptr非同寻常”[条款43]以及“禁止复制”条款 [32])。也就是说,对于一个类X而言,复制构造函数应该被声明为X(const X &),而复制赋值操作符则应该被声明为X &operator =(const X &)。通常来说,和传统的非成员形式的swap相比,如果成员形式的swap实现具有性能或异常安全的优势,那么定义一个成员函数swap往往是个好主意。典型的非成员形式的swap实现是很直观的:

  1. template <typename T> 
  2. void swap( T &a, T &b ) {  
  3.     T temp(a); // 调用T的复制构造函数  
  4.     a = b; // 调用T的复制赋值操作符  
  5.     b = temp; // 调用T的复制赋值操作符  

这个swap(与标准库中的swap一致)根据类型T的复制操作进行定义,如果T的实现短小简单,这种方式就会工作得很好;但如果T是一个庞大而复杂的类,这种方式就会花不小的开销。对于Handle这样的类,我们有更好的方式,那就是交换指向各自实现的指针。

  1. inline void Handle::swap( Handle &that )  
  2.     { std::swap( impl_, that.impl_ ); } 

还记得一个旧的喜剧片中讲到的“如何拿到一百万美元而又不用交税”这个情节吗?首先,拿到一百万美元……①下面的情景与它类似,展示了如何编写一个异常安全的复制赋值操作。首先,要得到一个异常安全的复制构造函数和一个异常安全的swap操作。剩下的事情就好办了:

  1. Handle &Handle::operator =( const Handle &that ) {  
  2.     Handle temp( that ); // 异常安全的复制构造  
  3.     swap( temp ); // 异常安全的swap  
  4.     return *this; // 我们假定temp的析构不会抛出异常  

对于句柄类(handle class)来说,这项技术工作得尤其好。句柄类是这样的一种类,它主要或全部是由一个指向其实现的指针构成。如前例所示,为句柄类编写异常安全的swap乃是小菜一碟,且效率极高。

对于复制赋值的这个实现来说,微妙之处在于复制构造的行为必须和复制赋值的行为“兼容”。尽管它们是不同的操作,然而此处存在一个影响深远的惯用假定,就是它们产生的结果不应该有区别。也就是说,不管是写成

  1. Handle a = ...  
  2. Handle b;  
  3. b = a; // 将a赋给b 

还是写成

  1. Handle a = ...  
  2. Handle b( a ); // 用a来初始化b 

b的结果值和将来的行为都应该没有差别,不管它是通过赋值还是通过初始化而得到那个值的。

当使用标准容器时,这种兼容性尤其重要,因为它们的实现常常用复制构造来代替复制赋值,当然也就期望两种操作产生一致的结果(参见“placement new”[条款35])。

一个或许更常见的复制赋值实现具有如下的结构:

  1. Handle &Handle::operator =( const Handle &that ) {  
  2.     if( this != &that ) {  
  3.         // 进行赋值……  
  4.     }  
  5.     return *this;  

这种对自身赋值所执行的检查往往是出于正确性的考虑(有时也是出于效率方面的考虑)。更确切地说,为了确保赋值表达式的左操作数(this)和右操作数(例如that)具有不同的地址。

大多数C++(www.cppentry.com)程序员在职业生涯里,都摆弄过实现虚拟复制赋值(virtual copy assignment)的想法。这种做法合法但过于复杂,所以别那么做。应该采用clone(克隆)取而代之(参见“虚构造函数与Prototype模式” [条款29])。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇条款12 赋值和初始化并不相同 下一篇条款14 函数指针

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: