Effective C++——条款10条,条款11和条款12(第2章)

条款10:    令operator=返回一个reference to *this

Have assignment operators return a reference to *this
 
    关于赋值,可以把它们写成连锁形式:
int x, y, z;
x = y = z = 15;        // 赋值连锁形式 
    赋值采用右结合律,所以上述连锁赋值被解析为:
x = (y = (z = 15)); 
    这里15先被赋值给z,然后其结果( 更新后的z)再被赋值给y,然后其结果(更新后的y)再被赋值给x.
     为了实现"连锁赋值",赋值操作符必须返回一个reference指向操作符的左侧实参,这是为classes实现赋值操作符时应该遵循的协议:
class Widget {
public:
    Widget& operator=(const Widget& rhs) {
        ...
        return *this;
    }
}; 
    这个协议不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算,例如:
class Widget {
public:
    ...
    Widget& operator+=(const Widget& rhs) {        // 这个协议适用于+=,-=,*=等等
        ...
        return *this;
    }
    Widget& operator=(int rhs) {                // 此函数也适用,即使参数类型不符规定
        ...
        return *this;
    }
};
  
  这只是个协议,并无强制性.如果不遵循它,代码一样可通过编译.然而这份协议被所有内置类型和标准程序库提供的类型共同遵守,所以最好还是遵守它.
    注意:
    令赋值(assignment)操作符返回一个reference to *this
.
 
 

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

Handle assignment to self in operator=
 
    "自我赋值"发生在对象被赋值给自己时
:
class Widget { ... };
Widget w;
...
w = w;            // 赋值给自己
    这看起来有点愚蠢,但它合法,所以不要认定客户绝不会那么做.此外赋值动作并不总是那么可被一眼辨识出来,例如:
a[i] = a[j];    // 潜在的自我赋值 
    而过i和j具有相同的值,这便是个自我赋值.再看:
*px = *py;        // 潜在的自我赋值 
    如果px和py恰巧指向同一个东西,这也是自我赋值.这些并不明显的自我赋值,是"别名"(aliasing)带来的结果:所谓"别名"就是"有一个以上的方法指向某对象".一般而言 如果某段代码操作pointers或references而它们被用来"指向多个相同类型的对象",就需要考虑这些对象是否为同一个.实际上两个对象只要来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成"别名",因为一个base class 的reference或pointer可以指向一个derived class 对象:
class Base { ... };
class Derived : public Base { ... };
void doSomething(const Base& rb, Derived* pd);    // rb和*pd可能是同一对象
    如果尝试自行管理资源,可能会掉进"在停止使用资源之前以外释放了它"的陷阱.假设建立一个 clas 来保存一个指针指向一块动态分配的位图:
class Bitmap { ... };
class Widget {
    ...
private:
    Bitmap* pb;        // 指针,指向一个从heap分配而得的对象
};
   下面是 operator= 实现代码,表面上看起来合理,但自我赋值出现时就不完全:
Widget &Widget::operator=(const Widget& rhs) {    // 一份不安全的operator=实现版本
    delete pb;
    pb = new Bitmap(&rhs.pb);
    return &this;
}
 
   这里的 自我赋值问题是,operator=函数内的*this(赋值的目的端)和rhs有可能是同一个对象.如果这样的话,delete 就不只是销毁当前对象的bitmap,它也销毁rhs的bitmap.在函数末尾,Widget发现自己持有一个指针指向一个已被删除的对象.
    欲阻止这种错误, 传统做法是借由 operator=最前面的一个"证同测试"达到"自我赋值"的检验目的:
Widget& Widget::operator=(const Widget& rhs) {
    if (this == &rhs)
        return *this;
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}
    这样做行得通. 但这个新版本仍然存在异常方面的麻烦.如果"new Bitmap"导致异常(不论是因为分配时内存不够或因为Bitmap的copy构造函数抛出异常), Widget最终会持有一个指针指向一块被删除的Bitmap.这样的指针有害,无法安全地删除它们,甚至无法安全地读取它们.
    令人高兴的是, 让 operator=具备"异常安全性"往往自动获得"自我赋值安全"的的回报.因此只需要把焦点放在实现"异常安全性"上.例如以下代码,只需要注意在复制pb所指东西之前别删除pb:
Widget& Widget::operator=(const Widget& rhs) {
    Bitmap* pOrig = pb;                    // 记住原先的pb
    pb = new Bitmap(*rhs.pb);        // 令pb指向*pb的一个副本
    delete pOrig;                                // 删除原先的pb
    return *this;
}
    现在,如果"new Bitmap"抛出异常,pb就维持原状.即使没有证同测试,这段代码还是能够处理自我赋值,因为对原bitmap做了一份复件,删除原bimap,然后指向新制造的那个复件.它或许不是处理"自我赋值"的最高效办法,但它行得通.
     在 operator=函数内手工排列语句(确保代码不但"异常安全"而且"自我赋值安全")的一个替代方案是,使用所谓的copy and swap技术.这个技术和"异常安全性"有密切关系,所以由条款29详细说明.然而由于它是一个常见而够好的 operator=撰写方法,所以值得看看其实现手法像什么样子:
class Widget {
    ...
    void swap(Widget& rhs);        // 交换*this和rhs的数据,详见条款29
};
Widget& Widget::operator=(const Widget& rhs) {
    Widget temp(rhs);            // 为rhs数据制作一份副本
    swap(temp);                    // 将*this数据和上述副本的数据交换
    return *this;
}
    这个主题的另一个变奏曲利用了以下事实:(1)某 class 的copy assignment操作符可能被声明为"以by value方式接受实参";(2)以by value方式传递东西会造成一份副本(详见条款20);
Widget& Widget::operator=(Widget rhs) {    // rhs是被传对象的一份副本
    swap(rhs);                            // pass by value
    return *this;
}
    它为了伶俐巧妙的修补而牺牲了清晰性,然而将"copying动作"从函数本体内移至"函数参数构造阶段"却可令编译器有时生成更高效的代码.
    (高效是否类似于返回值优化????)
     注意:
    确保当对象自我赋值时 operator=有良好行为.其中技术包括比较"来源对象"和"目标对象"的地址,精心周到的语句顺序,以及copy-and-swap.
    确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确.

 
 

条款12:    复制对象时勿忘其每一个成分

Copy all part of an object
 
    设计良好的面向对象系统会将对象的内部封装起来,只留两个函数负责对象拷贝,那便是带着适切名称的copy构造函数和copy assignment操作符.称它们为copying函数.条款5观察到编译器会在必要时候为class创建copying函数,并说明这些"编译器生成版"的行为:将被拷贝对象的所有成员变量都做一份拷贝.
    如果拒绝编译器写出copying函数,则需要在copying函数对所有变量都进行拷贝.为 class 添加一个成员变量,必须同时修改copying函数.
    因此在"为derived class撰写copying函数"时一定要非常小心地复制其base class 成分.那些成分往往是 private,所以无法直接访问它们,应该让derived class 的copying函数调用相应的base class 函数:
DerivedCustomer& DerivedCustom::operator=(const DerivedCustom& rhs) {
    Custom::operator=(rhs);
    ...
    return *this;
}
    本条款所说的"复制每一个成分"现在应该很清楚了.当编写一个copying函数,确保(1)复制所有local成员变量,(2)调用所有base classes内的适当的copying函数.
    如果发现copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者使用.这样的函数往往是 private 而且常被命名为init.这个策略可以安全消除copy构造函数和copy assignment操作符之间的代码重复.
     注意:
    copying函数应该确保复制"对象内的所有成员变量"以及"所有base class成分".
    不要尝试以某个copying函数实现另一个copying函数.应该将共同机能放进第三个函数中,并由两个copying函数共同调用. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值