Effective C++_條款15

條款15: 讓operator=返回*this的引用

c++的設計者bjarne stroustrup下了很大的功夫想使用戶自定義類型儘可能地和固定類型的工作方式相似。這就是爲什麼你可以重載運算符,寫類型轉換函數(見條款m5),控制賦值和拷貝構造函數,等等。他做了這麼多努力,那你最少也該繼續做下去。

讓我們看看賦值。用固定類型的情況下,賦值操作可以象下面這樣鏈起來:

int w, x, y, z;
w = x = y = z = 0;

所以,你也應該可以將用戶自定義類型的賦值操作鏈起來:

string w, x, y, z;    // string是由標準c++庫
            // “自定義”的類型
            // (參見條款49)


w = x = y = z = "hello";

因爲賦值運算符的結合性天生就是由右向左,所以上面的賦值可以解析爲:

w = (x = (y = (z = "hello")));

很值得把它寫成一個完全等價的函數形式。除非是個lisp程序員,否則下面的例子會很令人感到高興,因爲它定義了一箇中綴運算符:

w.operator=(x.operator=(y.operator=(z.operator=("hello"))));

這個格式在此很具有說明性,因爲它強調了w.operator=, x.operator=和y.operator=的參數是前一個operator=調用的返回值。所以operator=的返回值必須可以作爲一個輸入參數被函數自己接受。在一個類c中,缺省版本的operator=函數具有如下形式(見條款45):

c& c::operator=(const c&);

一般情況下幾乎總要遵循operator=輸入和返回的都是類對象的引用的原則,然而有時候需要重載operator=使它能夠接受不同類型的參數。例如,標準string類型提供了兩個不同版本的賦值運算符:

string&    // 將一個string
operator=(const string& rhs);    // 賦給一個string

string&    // 將一個char*
operator=(const char *rhs);    // 賦給一個string

請注意,即使在重載時,返回類型也是類的對象的引用。

c++程序員經常犯的一個錯誤是讓operator=返回void,這好象沒什麼不合理的,但它妨礙了連續(鏈式)賦值操作,所以不要這樣做。

另一個常犯的錯誤是讓operator=返回一個const對象的引用,象下面這樣:

class widget {
public:
...
const widget& operator=(const widget& rhs); 
...
};

這樣做通常是爲了防止程序中做象下面這樣愚蠢的操作:

widget w1, w2, w3;
...
(w1 = w2) = w3;        // w2賦給w1, 然後w3賦給其結果
            //(給operator=一個const返回值
            // 就使這個語句不能通過編譯)
這可能是很愚蠢,但固定類型這麼做並不愚蠢:
int i1, i2, i3;
...
(i1 = i2) = i3;    // 合法! i2賦給i1
            // 然後i3賦給i1!

這樣的做法實際中很少看到,但它對int來說是可以的,對我和我的類來說也可以。那它對你和你的類也應該可以。爲什麼要無緣無故地和固定類型的常規做法不兼容呢?

採用缺省形式定義的賦值運算符裏,對象返回值有兩個很明顯的候選者:賦值語句左邊的對象(被this指針指向的對象)和賦值語句右邊的對象(參數表中被命名的對象)。哪一個是正確的呢?

例如,對string類(假設你想在這個類中寫賦值運算符,參見條款11中的解釋)來說有兩種可能:

string& string::operator=(const string& rhs)
{
...
return *this;    // 返回左邊的對象
}
string& string::operator=(const string& rhs)
{
...
return rhs;    // 返回右邊的對象
}

對你來說,這好象是拿六個一和十二的一半來比較一樣爲難。實際上他們有很大的不同。

首先,返回rhs的那個版本不會通過編譯,因爲rhs是一個const string的引用,而operator=要返回的是一個string的引用。當要返回一個非const的引用而對象自身是const時,編譯器會給你帶來無盡的痛苦。看起來這個問題很容易解決——只用象這樣重新聲明operator=:

string& string::operator=(string& rhs) { ... }

這次又輪到用到它的應用程序不能通過編譯了!再看看最初那個連續賦值語句的後面部分:

x = "hello";    // 和x.op = ("hello"); 相同

因爲賦值語句的右邊參數不是正確的類型——它是一個字符數組,不是一個string——編譯器就要產生一個臨時的string對象(通過stirng構造函數——參見條款m19)使得函數繼續運行。就是說,編譯器必須產生大致象下面這樣的代碼:

const string temp("hello");    // 產生臨時string
x = temp;        // 臨時string傳給operator=

編譯器一般會產生這樣的臨時值(除非顯式地定義了所需要的構造函數——見條款19),但注意臨時值是一個const。這很重要,因爲它可以防止傳遞到函數內的臨時值被修改。否則,程序員就會很奇怪地發現,只有編譯器產生的臨時值可以修改而他們在函數調用時實際傳進去的參數卻不行。(關於這一點是有事實根據的,早期版本的c++允許這類的臨時值可以被產生,傳遞,修改,結果很多程序員感到很奇怪)

現在我們就可以知道如果string的operator=聲明傳遞一個非const的stirng參數,應用程序就不能通過編譯的原因了:對於沒有聲明相應參數爲const的函數來說,傳遞一個const對象是非法的。這是一個關於const的很簡單的規定。

所以,結論是,這種情況下你將別無選擇:當定義自己的賦值運算符時,必須返回賦值運算符左邊參數的引用,*this。如果不這樣做,就會導致不能連續賦值,或導致調用時的隱式類型轉換不能進行,或兩種情況同時發生。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章