首页 > 代码库 > 《Effective C++ 》学习笔记——条款11

《Effective C++ 》学习笔记——条款11

***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************



二、Constructors,Destructors and Assignment Operators


Rule 11:Handle assignment to self in operator =

规则11:在 operator= 中处理“自我赋值”



1.自我赋值?!

比如这样的:

class Widget  {  ...  };
Widget w;
...
w = w;    // 赋值给自己

这样做是允许的,所以不要期盼不会发生,因为鸟大了,什么林子都有 o(╯□╰)o。。


2.自我赋值 其他形式

① 除了最直观的   w=w

② 还有一些隐蔽的,比如:

a[i] = a[j];// 万一 i等于j

③ *px = *py 

或许,这两个指针都指向同一个对象

④ 还有 base class 与 derived class的:(因为一个基类的引用或者指针,可以指向派生类对象)

class Base  {  ...  };
class Derived : public Base  {  ...  };
void doSomething( const Base& rb , Derived* pd);<span style="white-space:pre">	</span>// rb 和 pd 可能指向同一个对象


总结下来就是,如果某段代码操作pointers 或 references,而它们被用来“指向多个相同类型的对象”,就需要考虑这些对象是否为同一个,而且如果对象来自同一个继承体系,它们甚至不需要声明为同类型就可能造成“别名”(如上面的④)。



3.愚蠢的自我赋值,会导致?

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

Widget& Widget::operator=( const Widget& rhs )<span style="white-space:pre">		</span>// 一分不安全的 operator= 实现版本
{
    delete pb;<span style="white-space:pre">						</span>// 停止使用当前的 Bitmap
    pb = new Bitmap(*rhs.pb);<span style="white-space:pre">				</span>// 使用 rhs's的Bitmap 副本
    return *this;<span style="white-space:pre">					</span>// 这个问题在上一个条款(条款10)中介绍过
}

这个例子说明了虾米呢?

就是,如果是

a = b;

工作原理是,先把a的版本删除,然后在将b赋值给a。

所以,如果a与b 是同一个东西,它就会导致错误,

按上面的例子来讲,就是,删除pb的同时,rhs也被删除了,所以第二行的赋值动作就会指向一个已经被删除的对象。



4.看到错误了,总归要解决的

有三种解决方案

<1> 第一种方案——“证同测试” identity test

Widget& Widget::operator=(const Widget& rhs )
{
    if( this == &rhs )    return *this;<span style="white-space:pre">		</span>// identity test
    
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}
就是在执行代码前,先判断是否是同一个东西,若是,就不需要做任何事情,直接返回。


<2> 第二种方案——先赋值,再删除

Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig = pb;<span style="white-space:pre">			</span>// 先备份
    pb = new Bitmap(*rhs.pb);<span style="white-space:pre">		</span>// 赋值
    delete pOrig;<span style="white-space:pre">			</span>// 删除备份
    return *this;
}
就是只需要注意在复制之前别删除原来的,先备份一下。这或许不是处理“自我赋值”最高效的办法,但它行得通。


<3> 第三种方案——copy and swap

class Widget  {
    ...
    void swap( Widget& rhs );<span style="white-space:pre">				</span>// 交换*this和rhs的数据,在后面条款29会有详解
    ...
};
Widget& Widget::operator=( const Widget& rhs )
{
    Widget temp(rhs);<span style="white-space:pre">					</span>// 为rhs数据制作一份复件
    swap(temp);
    return *this;
}

而且,还有另一种形式,对于:①某class的 copy assignment操作符声明为“以 by value 方式接受实参”;②以by value方式 传递东西会造成一份复件:

Widget& Widget::operator= ( Widget rhs )<span style="white-space:pre">	</span>// rhs是被传递对象的一份复件
{<span style="white-space:pre">		</span>
    swap(rhs);<span style="white-space:pre">					</span>// 注意这里是 pass by value
    return *this;<span style="white-space:pre">				</span>// 将*this的数据和复件的数据互换
}

而这种做法也是作者比较不推荐的,因为它为了 伶俐巧妙的修补 牺牲了 代码的清晰性,可读性略低。但 将 copying 动作 从函数本体 移至 函数参数构造阶段 却可以 让编译器产生更高效的代码。



5.请记住

★ 确保当对象自我赋值时 operator= 有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。

★ 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。



***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************


《Effective C++ 》学习笔记——条款11