# Item 11: 在 operator= 中處理 assignment to self(自賦值)
作者:Scott Meyers
譯者:fatalerror99 (iTePub's Nirvana)
發布:http://blog.csdn.net/fatalerror99/
當一個 object(對象)賦值給自己的時候就發生了一次 assignment to self(自賦值):
```
class Widget { ... };
Widget w;
...
w = w; // assignment to self
```
這看起來很愚蠢,但它是合法的,所以應該確信客戶會這樣做。另外,assignment(賦值)也并不總是那么容易辨別。例如,
```
a = a[j]; // potential assignment to self
```
如果 i 和 j 有同樣的值就是一個 assignment to self(自賦值),還有
```
*px = *py; // potential assignment to self
```
如果 px 和 py 碰巧指向同一個東西,這也是一個 assignment to self(自賦值)。這些不太明顯的 assignments to self(自賦值)是由 aliasing(別名)(有不止一個方法引用一個 object(對象))造成的。通常,使用 references(引用)或者 pointers(指針)操作相同類型的多個 objects(對象)的代碼需要考慮那些 objects(對象)可能相同的情況。實際上,如果兩個 objects(對象)來自同一個 hierarchy(繼承體系),甚至不需要公開聲明,它們就是相同類型的,因為一個 base class(基類)的 reference(引用)或者 pointer(指針)也能夠引向或者指向一個 derived class(派生類)類型的 object(對象):
```
class Base { ... };
class Derived: public Base { ... };
void doSomething(const Base& rb, // rb and *pd might actually be
Derived* pd); // the same object
```
如果你遵循 Item 13 和 14 的建議,你應該總是使用 objects(對象)來管理 resources(資源),而且你應該確保那些 resource-managing objects(資源管理對象)被拷貝時行為良好。在這種情況下,你的 assignment operators(賦值運算符)在你沒有考慮自賦值的時候可能也是 self-assignment-safe(自賦值安全)的。然而,如果你試圖自己管理 resources(資源)(如果你正在寫一個 resource-managing class(資源管理類),你當然必須這樣做),你可能會落入在你用完一個 resource(資源)之前就已意外地將它釋放的陷阱。例如,假設你創建了一個 class(類),它持有一個指向動態分配 bitmap(位圖)的 raw pointer(裸指針):
```
class Bitmap { ... };
class Widget {
...
private:
Bitmap *pb; // ptr to a heap-allocated object
};
```
下面是一個表面上看似合理 operator= 的實現,但如果出現 assignment to self(自賦值)則是不安全的。(它也不是 exception-safe(異常安全)的,但我們要過一會兒才會涉及到它。)
```
Widget&
Widget::operator=(const Widget& rhs) // unsafe impl. of operator=
{
delete pb; // stop using current bitmap
pb = new Bitmap(*rhs.pb); // start using a copy of rhs's bitmap
return *this; // see Item 10
}
```
這里的 self-assignment(自賦值)問題在 operator= 的內部,*this(賦值的目標)和 rhs 可能是同一個 object(對象)。如果它們是,則那個 delete 不僅會銷毀 current object(當前對象)的 bitmap(位圖),也會銷毀 rhs 的 bitmap(位圖)。在函數的結尾,Widget——通過 assignment to self(自賦值)應該沒有變化——發現自己持有一個指向已刪除 object(對象)的指針。
防止這個錯誤的傳統方法是在 operator= 的開始處通過 identity test(一致性檢測)來阻止 assignment to self(自賦值):
```
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this; // identity test: if a self-assignment,
// do nothing
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
```
這個也能工作,但是我在前面提及那個 operator= 的早先版本不僅僅是 self-assignment-unsafe(自賦值不安全)的,它也是 exception-unsafe(異常不安全)的,而且這個版本還有異常上的麻煩。詳細地說,如果 "new Bitmap" 表達式引發一個 exception(異常)(可能因為供分配的內存不足或者因為 Bitmap 的 copy constructor(拷貝構造函數)拋出一個異常),Widget 將以持有一個指向被刪除的 Bitmap 的指針而告終。這樣的指針是有毒的,你不能安全地刪除它們。你甚至不能安全地讀取它們。你對它們唯一能做的安全的事情大概就是花費大量的調試精力來斷定它們起因于哪里。
幸虧,使 operator= exception-safe(異常安全)一般也同時彌補了它的 self-assignment-safe(自賦值安全)。這就導致了更加通用的處理 self-assignment(自賦值)問題的方法就是忽略它,而將焦點集中于達到 exception safety(異常安全)。Item 29 更加深入地探討了 exception safety(異常安全),但是在本 Item 中,已經足以看出,在很多情況下,仔細地調整一下語句的順序就可以得到 exception-safe(異常安全)(同時也是 self-assignment-safe(自賦值安全))的代碼。例如,在這里,我們只要注意不要刪除 pb,直到我們拷貝了它所指向的目標之后:
```
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;
}
```
現在,如果 "new Bitmap" 拋出一個 exception(異常),pb(以及它所在的 Widget)的遺跡沒有被改變。甚至不需要 identity test(一致性檢測),這里的代碼也能處理 assignment to self(自賦值),因為我們做了一個原始 bitmap(位圖)的拷貝,刪除原始 bitmap(位圖),然后指向我們作成的拷貝。這可能不是處理 self-assignment(自賦值)的最有效率的做法,但它能夠工作。
如果你關心效率,你可以在函數開始處恢復 identity test(一致性檢測)。然而,在這樣做之前,先問一下自己,你認為 self-assignments(自賦值)發生的頻率是多少,因為這個檢測不是免費午餐。它將使代碼(源代碼和目標代碼)有少量增大,而且它將在控制流中引入一個分支,這兩點都會降低運行速度。例如,instruction prefetching(指令預讀),caching(緩存)和 pipelining(流水線操作)的效力都將被降低。
另一個可選的手動排列 operator= 中語句順序以確保實現是 exception- and self-assignment-safe(異常和自賦值安全)的方法是使用被稱為 "copy and swap" 的技術。這一技術和 exception safety(異常安全)關系密切,所以將在 Item 29 中描述。然而,這是一個寫 operator= 的足夠通用的方法,值得一看,這樣一個實現看起來通常就像下面這樣:
```
class Widget {
...
void swap(Widget& rhs); // exchange *this's and rhs's data;
... // see Item 29 for details
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); // make a copy of rhs's data
swap(temp); // swap *this's data with the copy's
return *this;
}
```
在這個主題上的一個變種利用了如下事實:(1)一個 clsaa(類)的 copy assignment(拷貝賦值運算符)可以被聲明為 take its argument by value(以傳值方式取得它的參數);(2)通過傳值方式傳遞某些東西以做出它的一個 copy(拷貝)(參見 Item 20):
```
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;
}
```
對我個人來說,我擔心這個方法在靈活的祭壇上犧牲了清晰度,但是通過將拷貝操作從函數體中轉移到參數的構造中,有時能使編譯器產生更有效率的代碼倒也是事實。
Things to Remember
* 當一個 object(對象)被賦值給自己的時候,確保 operator= 是行為良好的。技巧包括比較 source(源)和 target objects(目標對象)的地址,關注語句順序,和 copy-and-swap。
* 如果兩個或更多 objects(對象)相同,確保任何操作多于一個 object(對象)的函數行為正確。
- Preface(前言)
- Introduction(導言)
- Terminology(術語)
- Item 1: 將 C++ 視為 federation of languages(語言聯合體)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 確保 objects(對象)在使用前被初始化
- Item 5: 了解 C++ 為你偷偷地加上和調用了什么函數
- Item 6: 如果你不想使用 compiler-generated functions(編譯器生成函數),就明確拒絕
- Item 7: 在 polymorphic base classes(多態基類)中將 destructors(析構函數)聲明為 virtual(虛擬)
- Item 8: 防止因為 exceptions(異常)而離開 destructors(析構函數)
- Item 9: 絕不要在 construction(構造)或 destruction(析構)期間調用 virtual functions(虛擬函數)
- Item 10: 讓 assignment operators(賦值運算符)返回一個 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中處理 assignment to self(自賦值)
- Item 12: 拷貝一個對象的所有組成部分
- Item 13: 使用對象管理資源
- Item 14: 謹慎考慮資源管理類的拷貝行為
- Item 15: 在資源管理類中準備訪問裸資源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一個獨立的語句中將 new 出來的對象存入智能指針
- Item 18: 使接口易于正確使用,而難以錯誤使用
- Item 19: 視類設計為類型設計
- Item 20: 用 pass-by-reference-to-const(傳引用給 const)取代 pass-by-value(傳值)
- Item 21: 當你必須返回一個對象時不要試圖返回一個引用
- Item 22: 將數據成員聲明為 private
- Item 23: 用非成員非友元函數取代成員函數
- Item 24: 當類型轉換應該用于所有參數時,聲明為非成員函數
- Item 25: 考慮支持不拋異常的 swap
- Item 26: 只要有可能就推遲變量定義
- Item 27: 將強制轉型減到最少
- Item 28: 避免返回對象內部構件的“句柄”
- Item 29: 爭取異常安全(exception-safe)的代碼
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之間的編譯依賴
- Item 32: 確保 public inheritance 模擬 "is-a"
- Item 33: 避免覆蓋(hiding)“通過繼承得到的名字”
- Item 34: 區分 inheritance of interface(接口繼承)和 inheritance of implementation(實現繼承)
- Item 35: 考慮可選的 virtual functions(虛擬函數)的替代方法
- Item 36: 絕不要重定義一個 inherited non-virtual function(通過繼承得到的非虛擬函數)
- Item 37: 絕不要重定義一個函數的 inherited default parameter value(通過繼承得到的缺省參數值)
- Item 38: 通過 composition(復合)模擬 "has-a"(有一個)或 "is-implemented-in-terms-of"(是根據……實現的)
- Item 39: 謹慎使用 private inheritance(私有繼承)
- Item 40: 謹慎使用 multiple inheritance(多繼承)
- Item 41: 理解 implicit interfaces(隱式接口)和 compile-time polymorphism(編譯期多態)
- Item 42: 理解 typename 的兩個含義
- Item 43: 了解如何訪問 templatized base classes(模板化基類)中的名字
- Item 44: 從 templates(模板)中分離出 parameter-independent(參數無關)的代碼
- Item 45: 用 member function templates(成員函數模板) 接受 "all compatible types"(“所有兼容類型”)
- Item 46: 需要 type conversions(類型轉換)時在 templates(模板)內定義 non-member functions(非成員函數)
- Item 47: 為類型信息使用 traits classes(特征類)
- Item 48: 感受 template metaprogramming(模板元編程)
- Item 49: 了解 new-handler 的行為
- Item 50: 領會何時替換 new 和 delete 才有意義
- Item 51: 編寫 new 和 delete 時要遵守慣例
- Item 52: 如果編寫了 placement new,就要編寫 placement delete
- 附錄 A. 超越 Effective C++
- 附錄 B. 第二和第三版之間的 Item 映射