条款11: 在operator= 中处理"自我赋值"

来源:互联网 发布:如何关闭qq游戏端口 编辑:程序博客网 时间:2024/04/30 15:12

条款11: 在operator= 中处理"自我赋值"
        (Handle assignment to self operator=.)

内容:
    我们在写operator= 函数实现时,要注意一个问题:要考虑对象自我赋值的情况,因为客户完全可以写
下如下代码:
    Widget w;
    ...
    w=w;
    这样写完全合法,那么我们在写Widget::operator=(xx)的实现时,一定要考虑到这个问题,否则一些想
象不到的问题就来"拜访"你了,呵呵,比如,现在有一个类Widget:
    class Bitmap{};
    class Widget{
    public:       
        ...
        Widget& operator=(const Widget& rhs){
            delete hBitmap_;//hBitmap_可能为NULL,注意:delete 删除NULL指针是可以的,它会什么都不做.
            hBitmap_ = new Bitmap(*rhs.hBitmap_);
            return *this;
        }
    private:
        Bitmap* hBitmap_;
    };
    这里的rhs与*this如果要是同一个对象,会出现什么问题?也就是说两个对象的hBitmap_是同一个图片
数据,当delete hBitmap_;被执行时,它们指向的同一个Bitmap资源被释放掉,紧接着执行下面一条语句就出
现了问题,因为新产生的hBitmap_是一个无效对象(它占用的资源对象已经被释放掉了).程序继续往下跑的时
候就会可能出现访问内存等问题.这个时候你注意到了这个问题,你开始修改代码:
    Widget& Widget::operator=(const Widget& rhs){
        if(this == &rhs){ //增加了判断条件
            return *this;
        }
        delete hBitmap_;
        hBitmap_ = new Bitmap(*rhs.hBitmap_); // throw possibel exception?
        return *this;
    }
    代码增加了逻辑判断语句,这样就解决了"自我赋值安全"问题,然而如果new Bitmap(*rhs.hBitmap_);这条语
句出现了异常,那么hBitmap_就是一个不可预料值,这样就很可怕,导致了"异常性安全"问题的出现.
    往往让operator=获得了"异常安全性"却会自动获得"自我赋值安全"的回报,所以很多人把焦点放在了"异常安全性",
而对于"自我赋值安全性"就放任其不管,于是他们想出这样的方案:在赋值之前先不去删除以前的对象.这样就写出
如下代码:
    Widget& Widget::operator=(const Widget& rhs){
        Bitmap* holdBitmap_ = hBitmap_;
        hBitmap_ = new Bitmap(*rhs.hBitmap_);
        delete holdBitmap_;
        return *this;
    }
    这样的话,如果"new Bitmap(*rhs.hBitmap_);"抛出异常,左右操作对象都保持不变,保持了"异常安全性",而它也保持
了"自我赋值安全性",这行得通,不过你要是考虑到效率问题的话,有一种注重"异常安全性"的替代方案将是你的更好的
选择,它是通过"copy and swap"实现的(条款29将会做更详细的探讨):
    class Widget{
    public:
        Widget& operator=(const Widget& rhs){
            Widget tempt(rhs); //make a source copy
            swap(rhs);
            return *this;
        }
        ...
    private:
        void swap(const Widget& rhs); //交换两个Widget对象数据
        ...
    };
    由于改实现要将产生一个参数的副本所以说我们可以通过"pass by value"来传递参数,我们再次改写代码:
    class Widget{
    public:
        Widget& operator=(Wiget rhs){
            swap(rhs);
            return *this;
        }       
        ....
    private:
        void swap(const Widget& rhs); //交换两个Widget对象数据
        ...
    };
    这样的函数签名看起来虽然看起来让人不是那么舒服,虽然它为了巧妙的修改代码却牺牲了代码可读性,然而将
"copying动作"从函数本体移至"函数参数构造阶段"却可令编译器有时生成更高效代码.
   
请记住:
    ◆ 确保当对象自我赋值时operator=有良好行为.其中技术包括比较"来源对象"和"目标对象"的地址、精心周到的语
句顺序、以及copy-and-swap.
    ◆ 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确.

原创粉丝点击