条款46:需要类型转换的时候请为模板定义非成员函数
看看下面这个例子:
1 template<typename T> 2 class Rational{ 3 public: 4 Rational(const T & numerator, const T & denominator); 5 const T numerator()const; 6 const T denominator() const; 7 }; 8 template<typename T> 9 const Rational<T> operator*(const Rational<T>& lhs, 10 const Rational<T>& rhs); 11 Rational<int> oneHalf(1,2); 12 Rational<int> result = oneHalf*2;
上面的最后一个式子看起来好像能够通过编译,但是实际上不能,因为模版的关系,编译器不会知道我们想调用的是哪个函数。
上面式子能推倒出来,正确的可能就是编译器使用Rational<int>的non-explicit构造函数将2转换,但是编译器进行实参推倒的时候不会将隐式转换放到考虑的范围里面。
而解决上述问题的有效方法就是,不要让operator*需要去进行参数的推倒,而是将其设为Rational的一个friend函数:
1 template<typename T> 2 class Rational{ 3 public: 4 Rational(const T & numerator, const T & denominator); 5 const T numerator()const; 6 const T denominator() const; 7 friend 8 const Rational operator*(const Rational<T> & lhs, 9 const Rational<T> & rhs); 10 };
这样,当oneHalf声明出来的时候,就相当于自动声明出来了上面的operator*,这样的隐式转换同样也来能够成功。
在一个class template里面,template名称可以被用来当作“template何其参数的建议表达形式”,所以说上面的形式和下面的
1 friend 2 const Rational<T> operator*(const Rational<T> & lhs, 3 const Rational<T> & rhs);
const Rational<T> & rhs);
实际上是相同的。
但实际上上面咋Rational外部去给operator*一个定义是行不通的,因为模板的原因,这个定义必须被放置在类的内部,就像下面这样:
1 template<typename T> 2 class Rational{ 3 public: 4 Rational(const T & numerator, const T & denominator); 5 const T numerator()const; 6 const T denominator() const; 7 friend 8 const Rational<T> operator*(const Rational<T> & lhs, 9 const Rational<T> & rhs) 10 { 11 return Rational(lhs.numerator() * rhs.numerator(), 12 lhs.denominator() * rhs.denominator()); 13 } 14 };
这里的使用friend实际上有其独到的意义:为了让类型转换可以发生在所有的实参身上,需要一个non-member,为了让这个函数被自动具体化,我们需要将他们声明在class内部,而在class内部声明non-member函数的唯一方法就是将他声明称friend。!!
小结:当编写一个class template的时候,起所提供的“与此template相关的”函数支持“所含参数的隐式类型”时,应该将那些函数定义为“class template 内部的”friend函数
。