Item 24 指出:若所有參數都需隱式轉換,函數應為非成員函數。推廣到模板:
template <typename T>
class Rational {
public:
Rational(const T& num = 0, const T& den = 1);
const T numerator() const;
const T denominator() const;
};
template <typename T>
const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs);
調用 Rational<int> r = oneHalf * 2; 失敗!編譯器無法從 2 推導模板參數 T(需先實例化函數模板,再考慮隱式轉換,兩階段矛盾)。
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());
}
// ... 其他成員
};
關鍵點:
T 已知,友元函數簽名固定為 Rational<T>,編譯器可直接匹配。inline,適用簡短函數。若函數體較大,不宜內聯,可轉發外部輔助模板:
template <typename T> class Rational; // 前置聲明
template <typename T>
const Rational<T> doMultiply(const Rational<T>&, const Rational<T>&); // 輔助
template <typename T>
class Rational {
public:
friend const Rational operator*(const Rational& lhs, const Rational& rhs) {
return doMultiply(lhs, rhs); // 轉發,此處 T 已知
}
};
doMultiply 不支持混合模式,但只被內部 operator* 調用,參數已統一為 Rational<T>。
| 場景 | 方案 | 關鍵 |
|---|---|---|
| 非模板類 | 非成員友元函數 | Item 24 |
| 類模板,簡短函數 | 類內友元定義 | 隱式 inline |
| 類模板,複雜函數 | 類內友元轉發外部輔助模板 | 分離接口與實現 |
C++ 編譯流程:模板實例化 → 函數簽名確定 → 參數匹配(含隱式轉換)。友元定義於類內,實例化類時同步實例化友元函數,打破「推導模板參數需先知函數簽名」的死循環。