2012-03-26 66 views
1

在經歷Effective C++(Scott Meyers)期間,我遇到以下代碼,作者使用它來說明如何在將數據成員從一個對象複製到另一個時處理異常。處理複製賦值運算符中的異常(C++)

class Bitmap { ... }; 

class Widget { 
    ... 

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

Widget& Widget::operator=(const Widget& rhs) 
{ 
    Bitmap *pOrig = pb;    // remember original pb 
    pb = new Bitmap(*rhs.pb);   // make pb point to a copy of *pb 
    delete pOrig;      // delete the original pb 
    return *this; 
} 

如果「新位圖」引發異常,pb將保持不變。但是,通過刪除 pOrig,pb點已被釋放的內存。這不危險嗎?它是如何比任何下面的代碼

Widget& Widget::operator=(const Widget& rhs) 

{ 
    if (this == &rhs) return *this; // identity test: if a self-assignment, 
            // do nothing 
    delete pb; 
    pb = new Bitmap(*rhs.pb); 
    return *this; 
} 

這(他聲稱)是不好的,因爲當「新位圖」產生的異常更好(或者因爲沒有用於分配或因爲位圖的拷貝構造函數拋出一個內存不足) ,Widget最終將持有一個指向已刪除位圖的指針

我檢查了勘誤表,但未發現此示例。我錯過了明顯的東西嗎?另外,有人可以提出一個更好的方法來處理這個異常?

回答

2

當且僅當pb = new Bitmap(*rhs.pb);成功時纔會執行delete pOrig;。如果分配失敗,那麼不再有這個ctor會執行 - 相反,堆棧將被解開,並且執行將從構造函數的任何部分拋出異常,直接返回到拋出異常的處理程序。沿途唯一的停止將破壞局部變量的變量,但由於唯一的局部變量是一個指針,所以摧毀它幾乎就是一個nop。

在Widget對象包含任何其他成員變量的情況下,那些已完全構造的任何一個也會作爲堆棧展開的一部分被銷燬,但是由於它沒有任何(顯示)在這裏是不相關的。

+0

明白了,謝謝。 – Sam 2012-03-26 02:24:23

0

異常安全的主要原則是,您不希望在可能拋出異常的操作之前更改記錄數據。

第二個例子沒有通過這個,因爲delete變異了記錄的數據,後面跟着new運算符,可能會拋出數據,導致數據處於不完整狀態。

第一個例子沒有這個問題,因爲沒有任何數據會被突變,直到可能拋出的new操作。數據不可能保持不完整狀態。

0

在這種「新位圖」拋出一個異常的情況下,PB將保持不變 。但是,通過刪除pOrig,pb指向 的內存已被釋放。這不危險嗎?

不,你做了一個關於pOrig被刪除的錯誤假設,並且可能拋出異常。在原始代碼:

Widget& Widget::operator=(const Widget& rhs) 
{ 
    Bitmap *pOrig = pb; // <-- this can't throw 
    pb = new Bitmap(*rhs.pb); // <-- this can throw 
    delete pOrig; // <-- this can't throw 
    return *this; // <-- this can't throw 
} 

調用operator new這裏是哪裏的代碼可能會拋出的唯一地方。如果有,pb將不會被分配結果。它將指向前一個位圖,並且該類將保持有效狀態。它也不會繼續刪除指針。結果是,如果拋出異常,則不會發生泄漏,並且該類保持有效狀態。

至於你的代碼,它不是例外的安全。

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

一旦釋放與pb相關聯的內存,你把類處於無效狀態。因此,在您將課程恢復到有效狀態之前進行投擲是非常危險的。如果operator new在此處拋出,則由於pb是懸掛指針而導致您被擰緊,並且Widget類處於無效狀態。這就好像你根本沒有執行第二行。

幫你一個忙,並節省自己的心痛。使用RAII和智能指針,這會容易得多。

Widget& Widget::operator=(const Widget& rhs) 
{ 
    unique_ptr<Bitmap> new_bitmap(new Bitmap(*rhs.pb)); 
    pb.swap(new_bitmap); // make pb a unique_ptr as well 
    return *this; 
}