這個條款能夠當作是條款24的續集,咱們先簡單回顧一下條款24,它說了爲何相似於operator *這樣的重載運算符要定義成非成員函數(是爲了保證混合乘法2*SomeRational或者SomeRational*2均可以經過編譯,2不能同時進行隱式類型轉換成某個Rational,再做this用)。函數
因此咱們通常將之定義成友元函數,像下面這樣:this
1 class Rational 2 { 3 private: 4 int numerator; 5 int denominator; 6 public: 7 Rational(int n = 0, int d = 1): numerator(n), denominator(d){assert(denominator != 0);} 8 int GetNumerator() const{return numerator;} 9 int GetDenominator() const {return denominator;} 10 friend const Rational operator* (const Rational& r1, const Rational& r2); 11 }; 12 const Rational operator* (const Rational& r1, const Rational& r2) 13 { 14 return Rational(r1.numerator * r2.numerator, r1.denominator * r2.denominator); 15 }
如今咱們來引入模板,能夠像下面這樣寫,注意這裏的operator*是一個獨立的模板函數:spa
1 template <class T> 2 class Rational 3 { 4 private: 5 T Numerator; 6 T Denominator; 7 8 public: 9 Rational(const T& Num = 0, const T& Den = 1) : Numerator(Num), Denominator(Den){} 10 const T GetNumerator() const 11 { 12 return Numerator; 13 } 14 15 const T GetDenominator() const 16 { 17 return Denominator; 18 } 19 20 string ToString() const 21 { 22 stringstream ss; 23 ss << Numerator << "/" << Denominator; 24 return ss.str(); 25 } 26 }; 27 28 template <class T> 29 const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b) 30 { 31 return Rational<T>(a.GetNumerator() * b.GetNumerator(), 32 a.GetDenominator() * b.GetDenominator() ); 33 }
但下面main函數的兩行卻都不能經過編譯:code
1 int main() 2 { 3 Rational<int> a(3, 5); 4 Rational<int> c = a * 2; // 不能經過編譯! 5 c = 2 * a; // 不能經過編譯! 6 cout << c.ToString() << endl; 7 }
緣由是編譯器推導T出現了困難,a * 2在編譯器看來,能夠由a是Rational<int>將T推導成int,可是2是什麼,理想狀況下編譯器會嘗試將它先轉換成一個Rational<int>,並將T推導成int,但事實上編譯器在「T推導過程當中從不將隱式類型轉換函數歸入考慮」。因此不管是a * 2仍是2 * a都是不能經過編譯的,一句話,隱式轉換+推導T不能被同時被編譯器接受。blog
解決問題的思路便接着產生,編譯器既然不能同時接受這兩個過程,就讓它們事先知足好一個條件,再由編譯器執行另外一個過程好了。ip
若是把這個operator*放在template class裏面,也就是先在生成模板類的那一步就定下T,這樣編譯器只要執行隱式轉換這一步就能夠了。編譯器
所以咱們能夠這樣來改:string
1 template <class T> 2 class Rational 3 { 4 … 5 friend Rational operator* (const Rational& a, const Rational& b); 6 }; 7 8 template <class T> 9 const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b) 10 { 11 // 這裏友元函數的聲明並非用來訪問類的私有成員的,而是用來進行事先類型推導的 12 return Rational<T>(a.GetNumerator() * b.GetNumerator(), 13 a.GetDenominator() * b.GetDenominator() ); 14 }
注意紅色部分,咱們添加了一個友元函數的聲明,果真編譯經過了,但連接時又報錯了,緣由是連接器找不到operator*的定義,這裏又要說模板類中的一個特殊狀況了,它不一樣與普通的類,模板類的友元函數只能在類中實現,因此要把函數體部分移至到類內,像下面這樣:io
1 template <class T> 2 class Rational 3 { 4 … 5 friend Rational operator* (const Rational& a, const Rational& b) 6 { 7 return Rational (a.GetNumerator() * b.GetNumerator(), 8 a.GetDenominator() * b.GetDenominator()); 9 } 10 … 11 }
這下編譯和連接都沒有問題了。這裏還要說一下,就是移至類內後,T的標識符能夠不寫了,但若是非要寫成下面這樣,天然也是OK的。編譯
1 friend Rational<T> operator* (const Rational<T>& a, const Rational<T>& b) 2 { 3 return Rational<T>(a.GetNumerator() * b.GetNumerator(), 4 a.GetDenominator() * b.GetDenominator()); 5 }
operator*裏面只有一句話,但若是friend函數裏面的東西太多了,能夠定義一個輔助方法,好比DoMultiply(),這個DoMultiply能夠放在類外去實現,DoMultiply自己不支持混合乘法(2 * SomeRational或者SomeRational * 2),但因爲在operator*裏面已經進行了隱式類型轉換,因此到DoMultiply這一級是沒有問題的。
最後總結一下:
當咱們編寫一個class template,而它所提供之「與此template相關的」函數支持「全部參數之隱式類型轉換」時,請將那些函數定義爲「class template內部的friend函數」。