天天看點

在operator=中處理自我指派(Effective C++_11)

一、我們平時習慣寫的指派操作代碼

我們平時可能已經習慣了這樣的代碼,并且認為是對的:

#include<iostream>
using namespace std;

class Demo{
public:
    Demo& operator=(const Demo& x);
    Demo(int x=10,int y=15):a(x),b(y){
        p=new int;
    }
    void Print(){
        cout<<"a="<<a<<endl;
        cout<<"b="<<b<<endl;
    }
private:
    int a;
    int b;
    int* p;
};

Demo& Demo::operator=(const Demo& x){
    a=x.a;
    b=x.b;
    delete p;
    p=new int(*(x.p));
    return *this;

}

int main(){
    Demo obj(40,50);
    Demo obj2;
    obj2=obj;
    obj.Print ();
    obj2.Print();
    return 0;

}           

二、存在的問題

确實,以上代碼運作能通過,指派操作也能完美運作。但是,隻是表面上看起來合理而已,當出現自我指派時并不安全(實際上,也不具備異常安全性)。上面代碼中,有可能*this和x是同一對象,假如真的如此,delete不僅僅删除了目前對象的Demo,也删除了x的Demo,這樣,這函數末尾,持有的指針會指向一個已經删除的對象,(注:但是用obj=obj時,也能運作通過,不知道為什麼??,先按書中的來吧)

三、解決方法

為了解決這種錯誤,我們需要這樣做:

Demo& Demo::operator=(const Demo& x){
    if(this==&x)//從位址上判斷是否指向同一塊記憶體
        return *this;
    a=x.a;
    b=x.b;
    delete p;
    p=new int(*(x.p));
    return *this;

}

           

上述方法确實能解決自我指派問題,但是還不具備異常安全性,假設p=new int(*(x.p))出現異常,那麼最終會導緻Demo持有一個指針指向被删除的一塊Int,為了解決異常問題,可以采取下面的政策:

Demo& Demo::operator=(const Demo& x){
    if(this==&x)
        return *this;//可以省略這段防止自我指派的代碼
    a=x.a;
    b=x.b;
    int* temp=p;//儲存舊指針
    p=new int(*(x.p));//如果new操作發生異常,則仍然指向原來的空間
    delete temp;//删除原先的temp
    return *this;

}           

注意上述代碼中即使沒有

if(this==&x)
        return *this;           

也是行的通的,因為我們對原來的指針做了一個備份,删除舊的,指向新的;

四、最優方法

解決自我指派和異常問題還有兩個版本,即使用所謂的copy and swap技術

Demo& operator= (const Demo& x)
 {
          Demo temp(x);
          swap(*this, temp);
          return *this;
 }           

copy and swap的原理:為你打算修改的對象做出一份副本,然後在那份副本身上做出一切修改,若有任何修改動作抛出異常,原對象仍然保持未修改的狀态;待所有改變都成功後,再将修改過的那一個副本和原對象在一個抛出異常給的操作中置換(swap)(具體參見條款29);這裡把所有負擔都落在了複制構造函數上,如果出現異常,那麼swap就不會執行;

另一個版本是

Demo& operator= (const Demo x)
 {
          swap(*this, temp);
          return *this;
 }           

注意,這個版本和上面的有所不同,參數是通過傳值,而傳值操作會生成一份複件,即此時的x是原對象的一份副本(會調用指派構造函數)(傳值方式請參考條款20),那麼申請臨時變量的任務就落在形參上了;這樣的方式犧牲了代碼的清晰性,而增加了代碼的高效;

參考:Effective C++ 3rd(侯捷譯)

繼續閱讀