Note of Justin

关于工作和读书的笔记

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  47 Posts :: 0 Stories :: 45 Comments :: 0 Trackbacks

留言簿(14)

搜索

  •  

积分与排名

  • 积分 - 50780
  • 排名 - 433

最新评论

阅读排行榜

评论排行榜

[原创文章欢迎转载,但请保留作者信息]
Justin 于 2009-11-12

第十项所言无他,就是要记得定义拷贝运算符时要返回对象自身的引用(*this)。原因很简单,你会有连着用=号的时候(a=b=c),如果不返回对象的引用这一串赋值式子就无法传递下去。

Item11说的也是拷贝运算符,不过侧重点在使用=号对自身赋值的特殊情况(a=a)。再继续下去之前Scott先是举了个很傻的例子:一个赋值函数

class  Bitmap  { // ..}

class  Widget  {
// ..
private :
   Bitmap 
* pb;
}

   
Widget
&
Widget::
operator = ( const  Widget &  rhs)
{
   delete pb;
   pb 
=   new  Bitmap( * rhs.pb);
   
return   * this ;
}

这样的一个傻傻赋值函数有两个问题:
   1. 在自赋值的时候是要出事的。(在自赋值的时候:pb被释放之后,紧接着就又被当作右值来构造新的对象……)
   2. 在发生异常的时候也是要出事的。(设想如果在new的过程中出错然后抛出异常,结果就是我们的pb成了野指针:它指向一个已经delete了的内存空间,你无法再次delete它,也不能读,因为你不知道你读到的是什么@#¥%)

于是就有了以下应付自赋值的策略:

  1. 在函数入口检查是否属于自拷贝(例如:检查指针是否指向同一片内存),如果是,啥也不干直接返回。否则属于正常情况的拷贝。偷个懒,用书上的术语:这样解决了self-assignment-unsafe的问题,但是没能避免exception-unsafe。
  2. 第二种方法比较简单,只是整理一下指令的顺序。但是却同时解决了自赋值和抛出异常带来的问题。继续无耻的抄写代码一段:
    Widget&
    Widget::
    operator=(const Widget& rhs)
    {
       Bitmap 
    *pOrig = pb;               // remember original pb
       pb = new Bitmap(*rhs.pb);      // make pb point to a copy of *pb
       delete pOrig;                         // delete the original pb
       return *this;
    }
    这样的做法在解决以上两个问题的同时却也降低了执行的效率:不论什么情况,这个赋值函数都要创建一个新的Bitmap对象。
    当然,Scott也辩证地道出了第一种方法的额外支出:判断语句必然地引入了程序的分支(branch),于是指令的预取(prefetch)、缓冲(caching)、流水线处理(pipelining)的效率就会被降低。
  3. Copy And Swap。很深奥的样子。实际上就是改赋值为交换。例子在下面:

    Widget&
    Widget::operator=(Widget rhs)    // rhs is a copy of the object
    {                                     // passed in — note pass by val
       swap(rhs);                           // swap *this's data with
                                                
    // the copy's
       return *this;
    }


    利用参数传值,隐性的构造了一个Widget对象。然后将新对象和本对象中的数据成员交换,达到为本对象赋值的效果。新的临时对象在跳出函数后自动销毁。刚才说的两个unsafe,都不会存在。
    不过又要回来说效率,我总觉得这样开销还是大了,无论什么时候都要构造新的对象。而且Scott本人也说用swap来完成赋值的做法有点逻辑混淆。不过他老人家也说了,这样做很有可能让编译器生成更有效率的代码(!!)没有验证过,暂且相信吧【等待论证的占位符】

最后要炒的是第12项,讲的也还是拷贝运算符:如何保证在赋值/拷贝的时候能够将所有的成员完整拷贝过去?对于简单的数据成员,编译器自动生成的拷贝函数可以保证一个不漏都帮你拷贝;如果是比较复杂的成员(比如说指向一片内存空间的指针),编译器就没有足够的智商把这些成员拷贝到另外一个对象中去了。

自己动手丰衣足食,还是要自己写。
然而人写的东西终究还是会有各种问题,比如说:

  1. 在增加类成员以后有可能忘记更新拷贝函数(典型丢三落四……),显然的结果就是新增加的数据成员没有在拷贝函数中得到关照,拷贝不完全。
  2. 子类的拷贝函数把自己的成员都拷贝了,但是却漏了把父类对象的成员拷贝到新的对象中。

第一个问题没人能帮忙,只能靠自己小心。
第二个问题,方法比较直接了:在子类的拷贝函数中调用父类的拷贝函数(老爸,劳烦您也拷贝一下吧~~),代码:

Widget &
Widget::
operator   =  (Widget src)  
{
   swap(src);                                   
// copy-and-swap
   WidgetParent:: operator   =  (src);       // invoking the parent's copy assignment operator
    return   * this ;
}


最后的最后,通常来说在拷贝函数和拷贝构造函数中的实现大多相同,大师就很贴心的提醒:不要在拷贝函数中调用拷贝构造函数或者反之。如果真的需要避免代码的重复,大可定义一个私有的函数来负责前面两者相同的部分。

posted on 2009-12-18 22:18 Justin.H 阅读(1177) 评论(0)  编辑 收藏 引用 所属分类: Effective C++ 炒冷饭

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理