您的位置:首页 > Web前端

Effecticve学习笔记_条款46:需要类型转换时请为模板定义非成员函数

2016-08-31 11:52 465 查看
  条款24已经讨论过为什么惟有non-member函数才有能力“在所有实参身上实施隐式类型转换”,这里还以Rational class的operator*函数为例。

  这里将Rational和operator*模板化了:

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> opearator* (const Rational<T>& lhs, const Rational<T>& rhs)
{......}


  下面的例子如同条款24一样,唯一不同的是Rational和operator*如今都变成了templates:

Rational<int> oneHalf(1,2); //这个例子来自条款24
Rational<int> result = oneHalf * 2; // 错误,无法通过编译


  上述过程无法通过编译,因为编译器这里不知道我们想要调用哪个函数。取而代之的是,它们试图想出什么函数被名为operator* 的template具现化(产生)出来。它们知道它们应该可以具现化某个“名为operator*并接受两个Rational< T >参数”的函数,但为完成这一具现化行动,必须先算出T是什么。

  以oneHalf进行推导,过程并不困难。operator* 的第一参数被声明为Rational< T >,而传递给operator* 的第一实参(oneHalf)的类型是Rational< int >,所以T一定是int。其他参数的推导则没有这么顺利。operator* 的第二参数被声明为Rational< T >,但传递给operator* 的第二实参(2)类型是int。由于template实参推导过程中从不将隐式类型转换函数纳入考虑,所以这里编译器并不会将2转换为Rational< int >,进而将T推导为int。一句话,隐式转换+推导T不能被同时被编译器接受。

  解决问题的思路便接着产生,编译器既然不能同时接受这两个过程,就让它们事先满足好一个条件,再由编译器执行另一个过程好了。

  如果把这个operator*放在template class里面,也就是先在生成模板类的那一步就定下T,这样编译器只要执行隐式转换这一步就可以了。

  因此我们可以这样来改:

  

template <class T>
class Rational
{
......
friend Rational operator* (const Rational& lhs, const Rational& rhs);
};

template <class T>
const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs)
{
// 这里友元函数的声明并不是用来访问类的私有成员的,而是用来进行事先类型推导的
return Rational<T>(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}


  注意中间我们添加了一个友元函数。果然编译通过了,但链接时又报错了,原因是链接器找不到operator*的定义,这里又要说模板类中的一个特殊情况了,它不同与普通的类,模板类的友元函数只能在类中实现,所以要把函数体部分移至到类内,像下面这样:

template <class T>
class Rational
{
…
friend Rational operator* (const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
…
}


  这下编译和链接都没有问题了。这里还要说一下,就是移至类内后,T的标识符可以不写了,但如果非要写成下面这样,自然也是OK的。

friend Rational operator* (const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}


  operator* 里面只有一句话,但如果friend函数里面的东西太多了,可以定义一个辅助方法,比如DoMultiply(),这个DoMultiply可以放在类外去实现,DoMultiply本身不支持混合乘法(2 * SomeRational或者SomeRational * 2),但由于在operator*里面已经进行了隐式类型转换,所以到DoMultiply这一级是没有问题的。

  

  总结:当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C-C++