下面是一個基類 Bitmap 和派生類 Widget, Widget 中定義了一個私有類型 (private) 指針 pb
class Bitmap { ... }; class Widget { ... private: Bitmap *pb; // ptr to a heap-allocated object };
當在 Widget 類中重載賦值操作符 "=" 時,需要考慮以下幾個方面
1 鏈式賦值 (chain of assignments)
整數 15 首先賦值給 z,得到新值的 z 再賦值給 y,接著得到新值的 y 最後再賦值給 x,如下所示:
int x, y, z; x = y = z = 15; // chain of assignments
上述鏈式賦值相當於如下代碼:
x = (y = (z = 15));
為了實現鏈式賦值,函數的返回值須是一個實例自身的引用,也即 *this; 同理,重載其它的復合賦值運算符 (如 +=, -=, *=, /=),也必須在函數結束前返回 *this
Widget& Widget::operator=(const Widget& rhs) { delete pb; // stop using current bitmap pb = new Bitmap(*rhs.pb); // start using a copy of rhs's bitmap return *this; }
2 自賦值
其次要考慮的是,關於自賦值 (self-assigment) 的情況,雖然顯式的自賦值並不常見,但潛在的自賦值仍需注意
Widget w; ... w = w; // explict assignment to self a[i] = a[j]; // potential assignment to self *px = *py; // potential assignment to self
解決方法是,在函數內加一個 if 語句,判斷當前實例 (*this) 和傳入的參數 rhs 是不是同一個實例,也即判斷是不是自賦值的情況
如果是自賦值,則不作任何處理,直接返回 *this;如果不是自賦值,首先釋放實例自身已有內存,然後再分配新的內存,如下所示:
Widget& Widget::operator=(cosnt Widget& rhs) { if (this == &rhs) return *this; // identity test: if a self-assignment, do nothing delete pb; pb = new Bitmap(*rhs.pb); return *this; }
3 異常安全
上例中,假如在分配內存時,因內存不足或 Bitmap 的拷貝構造函數異常,導致 "new Bitmap" 產生異常 (exception),則 pb 指向的是一個已經被刪除的 Bitmap
考慮異常安全,一個方法是先用 new 分配新內容,再用 delete 釋放如下代碼的內容,如下所示:當 "new Bitmap" 拋出一個異常時,pb 指針並不會改變
Widget& Widget::operator=(cosnt Widget& rhs) {
if (this == &rhs) return *this; // identity test
Bitmap *pOrig = pb; // remember original pb pb = new Bitmap(*rhs.pb); // make pb point to a copy delete pOrig; // delete the original pb return *this; }
如果不考慮效率的問題,那麼即使沒有對自賦值進行判斷的 if 語句,其後面的語句也足以應付自賦值的問題
4 拷貝-交換
上例中,因為效率的問題,保留了 if 語句,但實際上,因為自賦值出現的概率很低,所以上述代碼看似“高效”,其實並不然
最常用的兼顧自賦值和異常安全 (exception safety) 的方法是 “拷貝-交換” (copy-and-swap),如下所示:
Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); // make a copy of rhs's data std::swap(*this, temp); // swap *this's data with the copy's return *this; }
上述代碼使用的是標准庫的 swap 函數,當然也可以自定義 swap 函數
小結:
1) 重載類賦值操作符,首先考慮鏈式賦值 -- 函數返回 *this,其次考慮自賦值和異常安全 -- “拷貝-交換”
2) 被重載的類賦值操作符 "=" 必須定義為成員函數,其它的復合賦值操作符 (如 "+=", "-=" 等) 應該被定義為成員函數
參考資料:
<Effective C++_3rd> item 10, 11
<劍指 offer> 2.2.1