1. 程式人生 > 其它 >Effective C++ 條款11:在operator=中處理自我賦值

Effective C++ 條款11:在operator=中處理自我賦值

Effective C++ 條款11:在operator=中處理自我賦值

潛在的自我賦值

// 1
a[i] = a[j];	// i = j

// 2
*px = *py;		// py和px指向同一物件

// 3
class Base{};
class Derived: public Base{};
void doSomething(const Base& rb, const Derived& pd);
// rb和pd可能引用的同一個物件

自我賦值一個bug:如果類裡面有動態記憶體分配,那麼在賦值的時候,需要先delete掉原來的,再new一個新的,最後賦值。但如果是自我賦值,那麼在delete掉原來的記憶體的同時,需要賦的值也被delete了(因為都是同一塊記憶體)。

解決這個問題的方法就是在前面加個判斷

下面是個例子,假設我們有一個Bitmap類,一個Widget類。其中Widget有一個Bitmap的指標。

class Bitmap{};
class Widget {
public:
    Widget& operator=(const Widget& rhs);
private:
    Bitmap* pb;
};

// 防止自我賦值的
Widget& Widget::operator=(const Widget& rhs){
    if (this == *rhs) return *this;
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this
}

除此之外還有個問題。如果賦值的時候,new一塊新的空間失敗了,那麼pb會指向一塊被delete掉的空間。這樣的指標是有害的。

Widget& Widget::operator=(const Widget& rhs) {
    Bitmap *tmp = pb;
    pb = new Bitmap(*rhs.pb);
    delete tmp;
    return *this;
}

首先上面這個版本申請了一個臨時變數儲存原始的物件。然後new一個Bitmap並賦值。如果這裡出錯了,還沒到delete,其他的所有東西都保持原樣。如果沒有出錯,則再將原始的空間,通過這個臨時變數delete。這就解決了上面的問題。

然後它還取消了自我賦值的檢測。但是他依然可以處理自我檢測問題,假如兩個指標指向同一個物件,它也會先建立一個新的副本,賦值以後再刪除原來的版本。

copy and swap技術

這個技術需要保證swap函式是異常安全的,具體後面的條款會解釋,這裡只做瞭解。

Widget& Widget::operator=(const Widget& rhs) {
    Widget tmp(rhs);
    swap(tmp);
    return this;
}

最後提一點的是,既然我們要自己建立一個rhs的副本,那為什麼不直接以值傳參,利用編譯器為我們生成一個副本呢。

Widget& Widget::operator=(Widget rhs) {	// not reference
    swap(rhs);
    return this;
}

當然這寫法不被提倡,因為降低清晰性,讓系統變得複雜。