歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C++ 之 重載賦值操作符

C++ 之 重載賦值操作符

日期:2017/3/1 9:16:08   编辑:Linux編程

下面是一個基類 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

Copyright © Linux教程網 All Rights Reserved