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&// 將一個stringoperator=(const string& rhs);// 賦給一個stringstring&// 將一個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");// 產生臨時stringx = temp;// 臨時string傳給operator=
編譯器一般會產生這樣的臨時值(除非顯式地定義了所需要的建構函式——見條款19),但注意臨時值是一個const。這很重要,因為它可以防止傳遞到函數內的臨時值被修改。否則,程式員就會很奇怪地發現,只有編譯器產生的臨時值可以修改而他們在函數調用時實際傳進去的參數卻不行。(關於這一點是有事實根據的,早期版本的c++允許這類的臨時值可以被產生,傳遞,修改,結果很多程式員感到很奇怪)
現在我們就可以知道如果string的operator=聲明傳遞一個非const的stirng參數,應用程式就不能通過編譯的原因了:對於沒有聲明相應參數為const的函數來說,傳遞一個const對象是非法的。這是一個關於const的很簡單的規定。
所以,結論是,這種情況下你將別無選擇:當定義自己的賦值運算子時,必須返回賦值運算子左邊參數的引用,*this。如果不這樣做,就會導致不能連續賦值,或導致調用時的隱式類型轉換不能進行,或兩種情況同時發生。