一、非成员函数模板出错的例子
-
我们在条款24说过,对于Rational类来说,为了让所有的乘法操作都能通过操作,我们将operator*()函数定义为非成员函数版本(详情可以回过头再去看)。但是对于模板来说,这些规则可能不成立
- 例如下面我们把Rational和operator*()函数都定义为模板,代码如下:
//下面与条款24都相同,只是改为了模板
template<typename T>
class Rational {
public:
Rational(const T& numerator = 0, const T& denominator = 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> oneHalf(1, 2);
//在条款24中,Rational和operator*为非模板,此处代码可编译通过
//此处,Rational和operator*为模板,编译不通过
Rational<int> result = oneHalf * 2;
错误原因分析
- 错误的原因在于:上面的“oneHalf*2”没有找到匹配的operator*()函数,从而导致出错
- 原因:
- oneHalf的类型为Rational<int>,因此其匹配到operator*()函数模板,并将operator*()函数模板的模板类型T实例化为int
- 但是当运行到第二个参数的时候,2为int,operator*()函数模板无法根据int推断出T的类型
- 因此最终编译器找不到合适的operator*()函数而导致出错
- 在非模板的operator*()中,operator*()可以将2隐式转换为Rational对象(Rational提供了隐式转换,详情再去看条款24)。但是在模板中,这种隐式转换不被允许
- template实参推导取到决定性的因素
二、将模板函数声明为friend
- 在“一”中,我们使用的模板类和模板函数出错了,一种解决办法是将operator()*声明为Rational模板类的友元(friend)
- 下面我们进过两次修改,最终实现错误的代码
第一次修改(编译通过,但是链接时出错)
- 我们先将operator()*声明为Rational的友元。代码如下:
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); int main() { Rational<int> oneHalf(1, 2); //编译通过,但是链接时出错 Rational<int> result = oneHalf * 2; return 0; }
- 备注:上面friend生命中,我们没有在Rational后面添加<T>,这与Rational<T>是一样的,这两都可以
- 编译通过的原因:
- 当对象oneHalf被定义时,程序会生成一份Rational<int>类的定义,于是class Rational<int>被实例化出来
- class Rational<int>被实例化之后,friend operator*()函数也被自动声明出来(备注:只是声明而没有定义)
- friend operator*()函数声明出来之后,该函数为一个函数了,而不是一个模板了,因此我们可以将2传递给该函数(并且参数2可以隐式类型转换,将2转换为Rational)
- 于是上面的代码就可以编译通过了,但是链接时出错(下面介绍)
- 链接时出错的原因:
- 因为在class Rational<int>被实例化出来的时候,friend只是将operator*()声明了,但是没有定义,因此程序报错
第二次修改(正确版本)
- 上面的代码编译通过,但是链接通过,因为找不到operator*()的定义
- 解决链接出错的方法是:在friend函数的声明同时,定义这个函数。代码如下:
template<typename T> class Rational { public: //声明的时候,同时实现该函数(备注,该函数虽然在类中定义,但不属于成员函数) friend const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator()*lhs.denominator()); } };
- 此处friend的另一个好处:我们虽然将该函数声明为friend,但是却没有访问任何class的non-public成员,因此比较安全
三、令friend函数调用辅助函数(与inline相关)
- 我们曾在条款30中说过,如果函数定义在class中都会成为inline,因此上面我们定义在class中的friend operator()*也会称为inline
-
为了使inline带来的冲击最小化,我们可以:
- 我们可以在operator()*中什么事情都不做,将所完成的事情移动到另一个辅助函数中
- 这个辅助函数应该是一个template
-
代码如下:
-
为了使operator*()在类中的inline最优化,我们定义了一个辅助函数,在其中完成operator*()原本要完成的功能
-
doMultiply()不支持混合式操作(例如将Rational对象和一个int相乘),但是该函数只由operator*()调用,而operator*()支持混合操作,因此功能完善
-
//声明
template<typename T> class Rational
//函数声明(定义在下)
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs);
template<typename T>
class Rational {
public:
friend
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
//在其中调用doMultiply(),可使inline最优化
return doMultiply(lhs, rhs);
}
};
//完成原本operator*()要完成的功能
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
{
return Rational<T>(lhs.numerator()*rhs.numerator(),
lhs.denominator()*lhs.denominator());
}
四、总结
- 当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”