做類似下面的事時,就會發生自己給自己賦值的情況:
class x ;
x a;
a = a; // a賦值給自己
這種事做起來好象很無聊,但它完全是合法的,所以看到程式設計師這樣做不要感到絲
毫的懷疑。更重要的是,給自己賦值的情況還可以以下面這種看起來更隱蔽的形式
出現:a = b;
如果b是a的另乙個名字(例如,已被初始化為a的引用),那這也是對自己賦值,
雖然表面上看起來不象。這是別名的乙個例子:同乙個物件有兩個以上的名字。在
本條款的最後將會看到,別名可以以大量任意形式的偽裝出現,所以在寫函式時一
定要時時考慮到它。
在賦值運算子中要特別注意可能出現別名的情況,其理由基於兩點。其中之一是效
率。如果可以在賦值運算子函式體的首部檢測到是給自己賦值,就可以立即返回,
從而可以節省大量的工作,否則必須去實現整個賦值操作。例如,條款16指出,一
個正確的派生類的賦值運算子必須呼叫它的每個基類的的賦值運算子,所以在派生
類中省略賦值運算子函式體的操作將會避免大量對其他函式的呼叫。
另乙個更重要的原因是保證正確性。乙個賦值運算子必須首先釋放掉乙個物件的資
源(去掉舊值),然後根據新值分配新的資源。在自己給自己賦值的情況下,釋放
舊的資源將是災難性的,因為在分配新的資源時會需要舊的資源。
看看下面string物件的賦值,賦值運算子沒有對給自己賦值的情況進行檢查:
class string ;
// 忽略了給自己賦值的情況
// 的賦值運算子
string& string::operator=(const string& rhs)
看看下面這種情況將會發生什麼:
string a = "hello";
a = a; // same as a.operator=(a)
賦值運算子內部,*this和rhs好象是不同的物件,但在現在這種情況下它們卻恰巧
是同乙個物件的不同名字。可以這樣來表示這種情況:
*this data ------------> "hello/0"//
rhs data -----
賦值運算子做的第一件事是用delete刪除data,其結果將如下所示:
*this data ------------> ???//
rhs data -----
現在,當賦值運算子對rhs.data呼叫strlen時,結果將無法確定。這是因為data被
刪除的時候rhs.data也被刪除了,data,this->data 和rhs.data 其實都是同乙個
指標!從這一點看,情況只會越變越糟糕。
現在可以知道,解決問題的方案是對可能發生的自己給自己賦值的情況先進行檢查
,如果有這種情況就立即返回。不幸的是,這種檢查說起來容易做起來難,因為你
必須定義兩個物件怎麼樣才算是「相同」的。
你面臨的這個問題學術上稱為object identity,它在物件導向領域是個很有名的
論題。本書不是講述object identity的地方,但有必要提到兩個解決這個問題的
基本方法。
乙個方法是,如果兩個物件具有相同的值,就說它們是相同的(具有相同的身份)
。例如,兩個string物件如果都表示的是相同順序的字串行,它們就是相同的:
string a = "hello";
string b = "world";
string c = "hello";
a和c具有相同值,所以它們被認為是完全相同的;b和它們都不同。如果把這個定
義用到string類中,賦值運算子看起來就象這樣:
string& string::operator=(const string& rhs)
{if (strcmp(data, rhs.data) == 0) return *this;
值相等通常由operator==來檢測,所以對於乙個用值相等來檢測物件身份的類c來
說,它的賦值運算子的一般形式是:
c& c::operator=(const c& rhs)
{// 檢查對自己賦值的情況
if (*this == rhs) // 假設operator=存在
return *this;
注意這個函式比較的是物件(通過operator=),而不是指標。用值相等來確定對
象身份和兩個物件是否占用相同的記憶體沒有關係;有關係的只是它們所表示的值。
另乙個確定物件身份是否相同的方法是用記憶體位址。採用這個定義,兩個物件當且
僅當它們具有相同的位址時才是相同的。這個定義在c++程式中運用更廣泛,可能
是因為它很容易實現而且計算很快,而採用值相等的定義則不一定總具有這兩個優
c& c::operator=(const c& rhs)
{// 檢查對自己賦值的情況
if (this == &rhs) return *this;
它對很多程式都適用。
如果需要乙個更複雜的機制來確定兩個物件是否相同,這就要靠程式設計師自己來實現
。最普通的方法是實現乙個返回某種物件識別符號的成員函式:
class c {
public:
objectid identity() const; // 參見條款36
對於兩個物件指標a和b,當且僅當 a->identity() == b->identity()的時候,它
們所指的物件是完全相同的。當然,必須自己來實現objectids的operator==。
別名和object identity的問題不僅僅侷限在operator=裡。在任何乙個用到的函式
裡都可能會遇到。在用到引用和指標的場合,任何兩個相容型別的物件名稱都可能
指的是同乙個物件。下面列出的是別名出現的其它情形:
class base {
void mf1(base& rb); // rb和*this可能相同
...void f1(base& rb1,base& rb2); // rb1和rb2可能相同
// class derived: public base {
void mf2(base& rb); // rb和*this可能相同
// ...
int f2(derived& rd, base& rb); // rd和rb可能相同
// 這些例子剛好都用的是引用,指標也一樣。
可以看到,別名可以以各種形式出現,所以決不要忘記它或期望自己永遠不會碰到
它。也許你不會碰到,但我們大多數會碰到。而很明顯的一條是,處理它會達到事
半功倍的效果。所以任何時候寫乙個函式,只要別名有可能出現,就必須在寫**
時進行處理。
在operator 中處理自我賦值
自我賦值發生在 widget a a a 上面這種情況一般不會發生。但是如果 b i b j p1 p2i j 或者 p1和p2指向同乙個物件 自我賦值就發生了 class widget private bitmap pb 傳統的方法是借由operator 最前面的乙個認同測試 class widg...
11 在 operator 中處理「自我賦值」
確定任何函式如果操作乙個以上的物件,而其中多個物件是同乙個物件時,其行為仍然正確。自我賦值 很容易被使用,雖然沒有意義,但若不加以 處理會造成系統報錯。法 1 傳統做法 在 operator 裡做乙個 證同測試 identity test 達到 自我賦值 的檢測目的。widget widget op...
條款11 在operator 中處理「自我賦值」
結論1 確保當物件自我賦值時operator 有良好行為,其中技術包括比較 物件 和 目標物件 的位址 證同測試 精心周到的語句順序 以及copy and swap。例如 class bitmap class widget 證同測試 widget widget operator const widg...