C++ 之 重載賦值操作符


  Widget 中,有一個 Bitmap 型指針  pb

class Bitmap;

class Widget {
private:
    Bitmap *pb; // ptr to a heap-allocated object
};

1  重載 “op=” 

  在 Widget 類中重載 "=" 時,需考慮以下方面

1.1  鏈式賦值

  整數 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;
}

1.2  自賦值

  其次要考慮的是,關於自賦值的情況,雖然顯式的自賦值並不常見,但潛在的隱式自賦值仍需注意

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;
}

1.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); // 注意:"." 的優先級高於 "*" delete pOrig; // delete the original pb return *this; }

   如果不考慮效率的問題,那么即使沒有對自賦值進行判斷的 if 語句,其后面的語句也足以應付自賦值的問題

 

2  拷貝-交換

  上例中,因為效率的問題,保留了 if 語句,但實際上,因為自賦值出現的概率很低,所以上述代碼看似“高效”,其實並不然

  最常用的兼顧自賦值和異常安全 (exception safety) 的方法是 “拷貝-交換” (copy-and-swap),如下所示:

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;
}

2.1  std::swap

  std::swap 屬於標准算法,其實現如下:

namespace std 
{ template
<typename T> // typical implementation of std::swap void swap(T& a, T& b) // swaps a's and b's values { T temp(a); a = b; b = temp; } }

  以上有三個拷貝:首先拷貝 a 給 temp,然后拷貝 b 給 a,最后拷貝 temp 給 b

2.2  Widget::swap

  對於 Widget 類,實現兩個 Widget 對象的值交換,只需互換 Bitmap *pb 即可,這稱為 pimpl (pointer to implementation)

  首先,定義一個 swap 公有成員函數,如下:

void Widget::swap(Widget& other)
{
    using std::swap;
    swap(pb, other.pb);  // to swap Widgets, swap their pb pointers
}

  然后,模板特例化 std::swap 函數,調用上面的 swap 函數,實現指針互換

namespace std 
{
    template<>     // revised specialization of std::swap
    void swap<Widget>(Widget& a, Widget& b)        
    {
        a.swap(b); // to swap Widgets, call their swap member function
    }
}

 

3  智能指針

  綜上所述,重載賦值操作符,需要考慮鏈式賦值、自賦值和異常安全,頗為繁瑣

  一個簡化方法是,在 Widget 類中聲明一個智能指針

class Widget {
    ...
private:
    std::unique_ptr<Bitmap> pBitmap; // smart pointer
};

  此時,重載 "op=",則只需考慮鏈式賦值

Widget& Widget::operator=(const Widget& rhs) // copy operator=
{
    *pBitmap = *rhs.pBitmap;  // "." 的優先級高於 "*"
return *this; }

  理論上應該可行,尚未在實際項目中驗證 (留待后續測試...)

 

小結:

1) 重載類賦值操作符,首先考慮鏈式賦值 -- 函數返回 *this,其次考慮自賦值和異常安全 -- “拷貝-交換”

2) 考慮寫一個不拋異常的 swap 函數 (consider support for a non-throwing swap)

3) 被重載的類賦值操作符 "op=" 必須定義為成員函數,其它的復合賦值操作符 (如 "+=", "-=" 等) 應該被定義為成員函數

4) 類中使用智能指針,可大大簡化重載賦值操作符 “op=” 的實現

 

參考資料:

 <Effective C++_3rd> item 10, 11, 25

 <劍指 offer> 2.2.1

 <Effective Modern C++> item 22


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM