程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 讀書筆記 effective c++ Item 24 如果函數的所有參數都需要類型轉換,將其聲明成非成員函數

讀書筆記 effective c++ Item 24 如果函數的所有參數都需要類型轉換,將其聲明成非成員函數

編輯:關於C++

讀書筆記 effective c++ Item 24 如果函數的所有參數都需要類型轉換,將其聲明成非成員函數。本站提示廣大學習愛好者:(讀書筆記 effective c++ Item 24 如果函數的所有參數都需要類型轉換,將其聲明成非成員函數)文章只能為提供參考,不一定能成為您想要的結果。以下是讀書筆記 effective c++ Item 24 如果函數的所有參數都需要類型轉換,將其聲明成非成員函數正文


1. 將需要隱式類型轉換的函數聲明為成員函數會出現問題

使類支持隱式轉換是一個壞的想法。當然也有例外的情況,最常見的一個例子就是數值類型。舉個例子,如果你設計一個表示有理數的類,允許從整型到有理數的隱式轉換應該是合理的。在C++內建類型中,從int轉換到double也是再合理不過的了(比從double轉換到int更加合理)。看下面的例子:

 1 class Rational {
 2 
 3 public:
 4 
 5 Rational(int numerator = 0, // ctor is deliberately not explicit;
 6 
 7 int denominator = 1); // allows implicit int-to-Rational
 8 
 9 // conversions
10 
11 int numerator() const; // accessors for numerator and
12 
13 int denominator() const; // denominator — see Item 22
14 
15 private:
16 
17 ...
18 
19 };

 

你想支持有理數的算術運算,比如加法,乘法等等,但是你不知道是通過成員函數還是非成員函數,或者非成員友元函數來實現。你的直覺會告訴你當你猶豫不決的時候,你應該使用面向對象的特性。有理數的乘積和有理數類相關,所有將有理數的operator*實現放在Rationl類中看上去是很自然的事。但違反直覺的是,Item 23已經論證過了將函數放在類中的方法有時候會違背面向對象法則,現在我們將其放到一邊,研究一下將operator*實現為成員函數的做法:

1 class Rational {
2 
3 public:
4 
5 ...
6 
7 const Rational operator*(const Rational& rhs) const;
8 
9 };

 

(如果你不明白為什麼函數聲明成上面的樣子——返回一個const value值,參數為const引用,參考Item 3,Item 20和Item21)

這個設計讓你極為方便的執行有理數的乘法:

1 Rational oneEighth(1, 8);
2 
3 Rational oneHalf(1, 2);
4 
5 Rational result = oneHalf * oneEighth; // fine
6 
7 result = result * oneEighth; // fine

 

但是你不滿足。你希望可以支持混合模式的操作,例如可以支持int類型和Rational類型之間的乘法。這種不同類型之間的乘法也是很自然的事情。

當你嘗試這種混合模式的運算的時候,你會發現只有一半的操作是對的:

1 result = oneHalf * 2; // fine
2 
3 result = 2 * oneHalf; // error!

 

這就不太好了,乘法是支持交換律的。

2. 問題出在哪裡?

將上面的例子用等價的函數形式寫出來,你就會知道問題出在哪裡:

1 result = oneHalf.operator*(2); // fine
2 
3 result = 2.operator*(oneHalf ); // error!

 

oneHalf對象是Rational類的一個實例,而Rational支持operator*操作,所以編譯器能調用這個函數。然而,整型2卻沒有關聯的類,也就沒有operator*成員函數。編譯器同時會去尋找非成員operator*函數(也就是命名空間或者全局范圍內的函數):

1 result = operator*(2, oneHalf ); // error!

 

但是在這個例子中,沒有帶int和Rational類型參數的非成員函數,所以搜索會失敗。

再看一眼調用成功的那個函數。你會發現第二個參數是整型2,但是Rational::operator*使用Rational對象作為參數。這裡發生了什麼?為什麼都是2,一個可以另一個卻不行?

沒錯,這裡發生了隱式類型轉換。編譯器知道函數需要Rational類型,但你傳遞了int類型的實參,它們也同樣知道通過調用Rational的構造函數,可以將你提供的int實參轉換成一個Rational類型實參,這就是編譯器所做的。它們的做法就像下面這樣調用:

1 const Rational temp(2); // create a temporary
2 
3 // Rational object from 2
4 
5 result = oneHalf * temp; // same as oneHalf.operator*(temp);

 

當然,編譯器能這麼做僅僅因為類提供了non-explicit構造函數。如果Rational類的構造函數是explicit的,下面的兩個句子都會出錯:

1 result = oneHalf * 2; // error! (with explicit ctor);
2 
3 // can’t convert 2 to Rational
4 
5 result = 2 * oneHalf; // same error, same problem

 

這樣就不能支持混合模式的運算了,但是至少兩個句子的行為現在一致了。

然而你的目標是既能支持混合模式的運算又要滿足一致性,也就是,你需要一個設計使得上面的兩個句子都能通過編譯。回到上面的例子,當Rational的構造函數是non-explicit的時候,為什麼一個能編譯通過另外一個不行呢?

看上去是這樣的,只有參數列表中的參數才有資格進行隱式類型轉換。而調用成員函數的隱式參數——this指針指向的那個——絕沒有資格進行隱式類型轉換。這就是為什麼第一個調用成功而第二個調用失敗的原因。

3. 解決方法是什麼?

然而你仍然希望支持混合模式的算術運行,但是方法現在可能比較明了了:使operator*成為一個非成員函數,這樣就允許編譯器在所有的參數上面執行隱式類型轉換了:

 1 class Rational {
 2 
 3 ... // contains no operator*
 4 
 5 };
 6 
 7 const Rational operator*(const Rational& lhs, // now a non-member
 8 
 9 const Rational& rhs) // function
10 
11 {
12 
13 return Rational(lhs.numerator() * rhs.numerator(),
14 
15 lhs.denominator() * rhs.denominator());
16 
17 }
18 
19 Rational oneFourth(1, 4);
20 
21 Rational result;
22 
23 result = oneFourth * 2; // fine
24 
25 result = 2 * oneFourth; // hooray, it works!

 

4. Operator*應該被實現為友元函數麼?

故事有了一個完美的結局,但是還有一個揮之不去的擔心。Operator*應該被實現為Rational類的友元麼?

在這種情況下,答案是No。因為operator*可以完全依靠Rational的public接口來實現。上面的代碼就是一種實現方式。我們能得到一個很重要的結論:成員函數的反義詞是非成員函數而不是友元函數。太多的c++程序員認為一個類中的函數如果不是一個成員函數(舉個例子,需要為所有參數做類型轉換),那麼他就應該是一個友元函數。上面的例子表明這樣的推理是有缺陷的。盡量避免使用友元函數,就像生活中的例子,朋友帶來的麻煩可能比從它們身上得到的幫助要多。

5. 其他問題

如果你從面向對象C++轉換到template C++,將Rational實現成一個類模版,會有新的問題需要考慮,並且有新的方法來解決它們。這些問題,方法和設計參考Item 46。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved