程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 讀書筆記 effective c++ Item 46 如果想進行類型轉換,在模板內部定義非成員函數,effectiveitem

讀書筆記 effective c++ Item 46 如果想進行類型轉換,在模板內部定義非成員函數,effectiveitem

編輯:C++入門知識

讀書筆記 effective c++ Item 46 如果想進行類型轉換,在模板內部定義非成員函數,effectiveitem


1. 問題的引入——將operator*模板化

Item 24中解釋了為什麼對於所有參數的隱式類型轉換,只有非成員函數是合格的,並且使用了一個為Rational 類創建的operator*函數作為實例。在繼續之前建議你先回顧一下這個例子,因為這個條款的討論是對它的擴展,我們會對Item 24的實例做一些看上去無傷大雅的修改:對Rational和opeartor*同時進行模板化:

 1 template<typename T>
 2 class Rational {
 3 public:
 4 Rational(const T& numerator = 0, // see Item 20 for why params
 5 
 6 const T& denominator = 1);         // are now passed by reference
 7 
 8 const T numerator() const;          // see Item 28 for why return
 9 
10 
11 const T denominator() const; // values are still passed by value,
12 ... // Item 3 for why they’re const
13 };
14 template<typename T>
15 const Rational<T> operator*(const Rational<T>& lhs,
16 const Rational<T>& rhs)
17 { ... }

 

正如Item 24中討論的,我們想支持混合模式的算術運算,所以我們想讓下面的代碼通過編譯。這應該沒有問題,因為我們在Item 24中使用了相同的代碼。唯一的區別是Rational和operator*現在變成了模板:

1 Rational<int> oneHalf(1, 2);            // this example is from Item 24,
2 // except Rational is now a template
3 
4 Rational<int> result = oneHalf * 2; // error! won’t compile

 

2. 問題分析——模板參數演繹不能進行隱式轉換


但事實上上面的代碼不會通過編譯,這就表明了模板化的Rational和非模板版本有些地方還是不一樣的,確實是有區別的。在Item 24中,編譯器知道我們嘗試調用什麼函數(帶兩個Rational參數的operator*),但是這裡,編譯器不知道我們想要調用哪個函數。相反,它們嘗試確認從模板operator*中實例化出(也即是創建)什麼樣的函數。它們知道它們想實例化一些名字為operator*的函數,這些函數帶有兩個類型為Rational<T>的參數,但是為了進行實例化,它們必須確認T是什麼。問題是他們不可能知道

為了演繹出T類型,它們看到了調用operator*時傳遞的參數類型。在上面的例子中,兩個參數類型分別是Rational<int>(oneHalf的類型)和int(2的類型)。每個參數進行單獨考慮。

使用oneHalf進行演繹(deduction)很容易,operator*的第一個參數所需要的類型為Rational<T>,實際上這裡傳遞給operator*的第一個參數的類型是Rational<int>,所以T必須為int。不幸的是,對其他參數的演繹就沒有這麼簡單了,operator*的第二個參數所需要的類型也為Rational<T>,但是傳遞給operator*的第二個參數是一個int值。在這種情況下編譯器該如何確認T是什麼呢?你可能期望它們使用Rational<int>的非顯示構造函數來將2轉換為一個Rational<int>,這樣就允許它們將T演繹成int,但是它們沒有這麼做。因為在模板參數演繹期間永遠不會考慮使用隱式類型轉換函數。這樣的轉換是在函數調用期間被使用的,所以在你調用一個函數之前,你必須知道哪個函數是存在的。為了知道這些,你就必須為相關的函數模板演繹出參數類型(然後你才能實例化出合適的函數。)但是在模板參數演繹期間不會通過調用構造函數來進行隱式轉換Item 24沒有涉及到模板,所以模板參數的演繹不是問題。現在我們正在討論C++的模板部分(Item 1),這變為了主要問題。

 

3. 問題解決——使用友元函數

3.1 在類模板中聲明友元函數——編譯通過

 

我們可以利用如下事實來緩和編譯器接受的對模板參數演繹的挑戰:模板類中的一個友元聲明能夠引用一個實例化函數。這就意味著類Ration<T>能夠為Ration<T>聲明一個友元函數的operator*。類模板不再依賴於模板參數演繹(這個過程只應用於函數模板),所以T總是在類Ration<T>被實例化的時候就能被確認。所以聲明一個合適的友元operator*函數能簡化整個問題:

 1 template<typename T>
 2 class Rational {
 3 public:
 4 ...
 5 
 6 friend // declare operator*
 7 const Rational operator*(const Rational& lhs, // function (see
 8 const Rational& rhs); // below for details)
 9 };
10 
11 template<typename T>                                                              // define operator*
12 
13 const Rational<T> operator*(const Rational<T>& lhs, // functions  
14 
15 
16 const Rational<T>& rhs)
17 { ... }

 

現在我們對operator*的混合模式的調用就能通過編譯了,因為當對象oneHalf被聲明為類型Rational<int>的時候,Ratinonal<T>被實例化稱Rational<int>,作為這個過程的一部分,參數為Rational<int>的友元函數operator*被自動聲明。作為一個被聲明的函數(不是函數模板),編譯器在調用它時就能夠使用隱式類型轉換函數(像Rational的非顯示構造函數),這就是如何使混合模式調用成功的

3.2 關於模板和模板參數的速寫符號

雖然代碼能夠通過編譯,但是卻不能鏈接成功。我們稍後處理,但是對於上面的語法我首先要討論的是在Rational中聲明operator*。

在一個類模板中,模板的名字能夠被用來當作模板和模板參數的速寫符號,所以在Rational<T>中,我們可以寫成Rational來代替Rational<T>。在這個例子中只為我們的輸入減少了幾個字符,但是如果有多個參數或者更長的參數名字的時候,它既能減少輸入也能使代碼看起來更清晰。我提出這些是因為在上面的例子中operator*的聲明用Rational作為參數和返回值,而不是Rational<T>。下面的聲明效果是一樣的:

1 template<typename T>
2 class Rational {
3 public:
4 ...
5 friend
6 const Rational<T> operator*(const Rational<T>& lhs,
7 const Rational<T>& rhs);
8 ...
9 };

 

然而,使用速寫形式更加容易(更加大眾化)。

3.3 把友元函數的定義合並到聲明中——鏈接通過

現在讓我們回到鏈接問題。混合模式的代碼能夠通過編譯,因為編譯器知道我們想調用一個實例化函數(帶兩個Rational<int>參數的operator*函數),但是這個函數只在Rational內部進行聲明,而不是被定義。我們的意圖是讓類外部的operator*模板提供定義,但是編譯器不會以這種方式進行工作。如果我們自己聲明一個函數(這是我們在Rational模板內部所做的),我們同樣有責任去定義這個函數。在上面的例子中,我們並沒有提供一個定義,這就是為什麼連接器不能知道函數定義的原因。

最簡單的可能工作的解決方案是將operator*函數體合並到聲明中:

 1 template<typename T>
 2 class Rational {
 3 public:
 4 ...
 5 friend const Rational operator*(const Rational& lhs, const Rational& rhs)
 6 {
 7 return Rational(lhs.numerator() * rhs.numerator(), // same impl
 8 lhs.denominator() * rhs.denominator()); // as in
 9 } // Item 24
10 };
11 
12  

 

確實這能夠工作:對operator*的混合模式調用,編譯,鏈接,運行都沒有問題。

3.4 如此使用友元函數很意思

這種技術的有意思的地方是友元函數並沒有被用來訪問類的非public部分。為了使所有參數間的類型轉換成為可能,我們需要一個非成員函數(Item 24在這裡仍然適用);並且為了讓合適的函數被自動實例化出來,我們需要在類內部聲明一個函數。在類內部聲明一個非成員函數的唯一方法是將其聲明為友元函數。這就是我們所做的,不符合慣例?是的。有效麼?毋庸置疑。

4. 關於模板友元函數inline的討論

正如在Item 30中解釋的,在類內部定義的函數被隱式的聲明為inline函數,這同樣包含像operator*這樣的友元函數。你可以最小化這種inline聲明的影響:通過讓operator*只調用一個定義在類體外的helper函數。在這個條款的例子中沒有必要這麼做,因為operator*已經被實現成了只有一行的函數,對於更加復雜的函數體,helper才可能是你想要的。“讓友元函數調用helper”的方法值得一看。

Rationl是模板的事實意味著helper函數通常情況下也會是一個模板,所以在頭文件中定義Rational的代碼會像下面這個樣子:

 1 template<typename T> class Rational; // declare
 2 // Rational
 3 // template
 4 
 5 template<typename T> // declare
 6 const Rational<T> doMultiply( const Rational<T>& lhs, // helper
 7 
 8 const Rational<T>& rhs);                                         // template
 9 
10 template<typename T>
11 class Rational {
12 public:
13 ...
14 friend
15 const Rational<T> operator*(const Rational<T>& lhs,
16 const Rational<T>& rhs)                                          // Have friend
17 
18  
19 
20 { return doMultiply(lhs, rhs); }                // call helper
21 
22 ...                                                            
23 
24 };           

                                                 

許多編譯器從根本上強制你將所有的模板定義放在頭文件中,所以你可能同樣需要在你的頭文件中定義doMultiply。(正如Item30解釋的,這樣的模板不需要inline)。這會像下面這個樣子:

template<typename T> // define
const Rational<T> doMultiply(const Rational<T>& lhs, // helper
const Rational<T>& rhs) // template in
{ // header file,
return Rational<T>(lhs.numerator() * rhs.numerator(), // if necessary
lhs.denominator() * rhs.denominator());
}

當然,作為一個模板,doMultiply不支持混合模式的乘法,但是也不需要支持。它只被operator*調用,operator*支持混合模式操作就夠了!從根本上來說,函數operator*支持必要的類型轉換,以確保兩個Rational對象被相乘,然後它將這兩個對象傳遞到doMultiply模板的合適實例中進行實際的乘法操作。協同行動,不是麼?

5. 總結

當實現一個提供函數的類模版時,如果這些函數的所有參數支持和模板相關的隱式類型轉換,將這些函數定義為類模板內部的友元函數。

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