對於 Rational 類別,欲支援 oneHalf * 2 (混合模式),operator* 必須是非成員函式,否則 2 無法隱式轉換為 Rational。
當 Rational 與 operator* 皆為模板:
template<typename T> class Rational { ... };
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs);
Rational<int> oneHalf(1,2); Rational<int> result = oneHalf * 2; 編譯失敗!原因:編譯器需推演 T,第一參數推得 T=int,但第二參數是 int,無法推演 T —— 模板實參推演不考慮建構函式隱式轉換。
在 Rational<T> 內宣告 operator* 為友元:
template<typename T>
class Rational {
public:
// ...
friend
const Rational operator*(const Rational& lhs, const Rational& rhs);
};
// 類別外定義
template<typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs) { ... }
關鍵點:
Rational<int> oneHalf 實例化類別時,友元函式 operator*(const Rational<int>&, const Rational<int>&) 被自動宣告 (非模板)。Rational<int> 的 non-explicit 建構函式將 2 轉換,混合模式運算成功。僅宣告友元不定義會導致連結錯誤。最簡單修法:將實作直接寫在友元宣告內 (隱式 inline):
template<typename T>
class Rational {
public:
// ...
friend const Rational operator*(const Rational& lhs, const Rational& rhs) {
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
};
複雜函式可讓友元呼叫外部 helper 函式,減輕 inline 膨脹。
在 Rational<T> 內可直接寫 Rational 代替 Rational<T>,簡化友元簽名。
此處使用友元非為存取 private/protected,而是為了「在類別內宣告非成員函式」—— 這是 C++ 唯一能在類別內宣告非成員函式的語法。為了支援所有參數隱式轉換 (需非成員) + 模板實參推演失敗時的自動實例化 (需類別內宣告),友元成了最佳拍檔。