昨天偷懶了,今天補。
條款10:令operator=返回一個reference to *this
Have assignment operators return areference to *this
關於賦值的一件有意思的事情是你可以把它寫成連鎖形式。
int x, y, z;
x = y = z = 15; // 賦值連鎖形式,相當於x = (y = (z = 15));
這裡,15 賦給 z,然後將這個賦值的結果(最新的 z)賦給 y,然後將這個賦值的結果(最新的 y)賦給 x。
為了實現“連鎖賦值”,賦值操作符必須返回一個reference指向操作符的左側實參。這是為類實現賦值操作符時應該遵循的協議:
class Widget {
public:
...
Widget& operator=(const Widget& rhs)
{ // 返回類型是個reference,指向當前對象
...
return *this; // 返回左側對象
}
...
};
這個協議不僅適用於以上的標准賦值形式,也適用於所有賦值相關運算,例如:
class Widget {
public:
...
Widget& operator+=(const Widget& rhs) // 這個協議也適用於+=, -=, *=, 等等.
{
...
return *this;
}
Widget& operator=(int rhs) // 此函數也適用,即使此操作符的參數類型不符協定
{
...
return *this;
}
...
};
這只是個協議,並無強制性。不遵循代碼一樣可通過編譯。然而這份協議被所有內置類型和標准程序庫提供的類型如string,vector,complex,tr1::shared_ptr共同遵守。因此除非有個標新立異的好理由,不然還是隨眾吧。
· 讓賦值操作符返回一個reference to *this。
條款11:在operator=中處理“自我賦值”
Handle assignment to self in operator=
自我賦值發生在對象被賦值給自己時,它合法,所以不要認定客戶絕不會這麼做。此外賦值動作不是那麼簡單能一眼辨識出來:
a[i] =a[j]; // 如果i==j
*px = *py;// 如果px、py指向同一個東西
這些不太明顯的 自我賦值是由 aliasing(別名)(有不止一個方法引用一個對象)造成的。通常,使用引用或者指針操作相同類型的多個對象需要考慮那些對象可能相同的情況。實際上,如果兩個對象來自同一個繼承體系,甚至不需要聲明為相同類型就可能造成別名,因為一個基類的引用或者指針可以指向一個派生類對象:
class Base { ... };
class Derived: public Base { ... };
void doSomething(const Base& rb, Derived* pd);
// rb和*pd有可能其實是同一對象
如果你試圖自己管理資源(如果正在寫一個資源管理類),你可能會落入用完一個資源之前就已意外地將它釋放的陷阱。例如,假設你建立一個類用來保存一個指針指向一塊動態分配的位圖:
class Bitmap { ... };
class Widget {
...
private:
Bitmap *pb; // 指向一個從heap分配而得的對象的指針
};
傳統做法是在 operator= 的開始處通過 identity test(證同測試)來達到“自我賦值”的目的:
Widget& Widget::operator=(constWidget& rhs)
{
if (this == &rhs) return *this; // 證同測試:如果是自我賦值,就不做任何事
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
如果缺少證同測試那句語句, *this(賦值的目標)和 rhs 可能是同一個對象。果真如此 delete 不僅會銷毀當前對象的bitmap,也會銷毀 rhs 的 bitmap。在函數的結尾,Widget(原本不該被自我賦值動作改變的)發現自己持有一個指向已刪除對象的指針。
加上證同測試那句語句可保證“自我賦值安全性”,但不具備“異常安全性”。更明確地說,如果 "new Bitmap" 表達式引發一個異常(可能因分配時內存不足或者因為 Bitmap 的 copy構造函數拋出異常),Widget 最後會持有一個指針指向一塊被刪除的Bitmap。這樣的指針是不能安全地刪除,不能安全地讀取。
幸虧,使 operator=具備“異常安全性”往往自動獲得“自我賦值安全”。因而可以將焦點集中於達到異常安全性。本例中,我們只要注意復制pb所指東西之前別刪除pb:
Widget& Widget::operator=(constWidget& rhs)
{
Bitmap *pOrig = pb; // 記住原先的pb
pb = new Bitmap(*rhs.pb); // 令pb指向*pb的一個副本
delete pOrig; // 刪除原先的pb
return*this;
}
現在,如果"new Bitmap"拋出一個異常,pb(以及它所在的 Widget)保持原狀。即使沒有證同測試,這裡的代碼也能處理 自我賦值,因為我們做了一個原始bitmap的拷貝,刪除原始bitmap,然後指向我們作成的拷貝。這可能不是處理自我賦值的最有效率的做法,但它能夠工作。
另一個確保異常和自我賦值安全的方法是使用被稱為 "copy and swap" 的技術。這是一個寫 operator= 的常見且夠好的方法:
class Widget {
...
void swap(Widget& rhs); // 交換*this和rhs數據
...
};
Widget& Widget::operator=(constWidget& rhs)
{
Widget temp(rhs); // 為rhs數據制作一份副本
swap(temp); // 將*this數據和上述復件的數據交換
return *this;
}
以下的變種利用了如下事實:(1)一個類的 copy賦值操作符可以被聲明為“以值傳遞方式接受實參”;(2)通過值傳遞方式傳遞會生成一份副本:
Widget& Widget::operator=(Widget rhs)
{ // rhs是被傳對象的一份副本,注意這裡是值傳遞,將*this的數據和副本數據互換
swap(rhs);
return *this;
}
這個方法在靈活的祭壇上犧牲了清晰度,但是通過將拷貝操作從函數體中轉移到函數參數構造階段中,有時能使編譯器產生更有效率的代碼倒也是事實。
· 當一個對象自我賦值的時候,確保 operator= 行為良好。其中技術包括比較“來源對象”和“目標對象”的地址、精心周到的語句順序、以及 copy-and-swap。
· 如果兩個或更多對象相同,確保任何操作多於一個對象的函數行為正確。
摘自 pandawuwyj的專欄