读书笔记:Effective C++ 炒冷饭 - Iitem46 参数需要类型转换时为模板定义非成员函数

读书笔记:Effective C++ 炒冷饭 - Iitem46 参数需要类型转换时为模板定义非成员函数
[原创文章欢迎转载,但请保留作者信息]
Justin 于 2010-05-01


大师的课上到46堂了,今天课前要求复习一下24课的内容。
如果你懒得自己去看,这里大概提一下:24课说的是如果在实际调 用中,某个函数的任何一个参数都有可能是其他类型数据通过“类型转换”转换过来的,这个函数最好是写成非成员函数。(哪 怕回去24节看,扫一遍书上的例子也知道是什么意思了)

现在把24课的内容升级为模板,就有了下面的代码:
template  typename T >
class  Rational {
   
public :
      Rational(
const  T &  num  =   0 const  T &  denom  =   1 )
      {
         _numerator 
=  num;
         _denominator 
=  denom;
      }
#if  OPTION2
      friend 
const  Rational  operator * ( const  Rational &  lhs,  const  Rational &  rhs);
#endif
#if  OPTION3
friend 
const  Rational  operator   *  ( const  Rational &  lhs,  const  Rational &  rhs)
{
   
return  Rational(lhs.numerator()  *  rhs.numerator(), lhs.denominator()  *  rhs.denominator());
}
#endif

      
const  T numerator()  const  {  return  _numerator; }
      
const  T denominator()  const  {  return  _denominator; }

   
private :
      T _numerator;
      T _denominator;
      
// ..
};
#if  OPTION1 || OPTION2
template 
typename T >
const  Rational T >   operator   *  ( const  Rational T >&  lhs,  const  Rational T >&  rhs)
{
   
return  Rational T >  (lhs.numerator()  *  rhs.numerator(), lhs.denominator()  *  rhs.denominator());
}
#endif

int  main( void )
{
   Rational
int >  oneFourth( 1 4 );
   Rational
int >  result;
   result 
=  oneFourth  * 2 ;
// ..
}

当OPTION1为真时,就是item24中的实现方法:用非成员函数来使得所有的参数都可以接受类型转换。
但是编译不能通过。这说明模板C++的世界是不一样的: 对于以上代码的非模板版本,编译器只需要关心哪个函数可以调用就可以了;
而引入模板后, 由于一个模板函数可以有无数个实例,编译器首先要知道的是应该生成哪一个实例,然后才是调用。
OPTION1为真时的代码,这个模板函数在编译时期就会碰到问题:模板参数无法确定。本来说好了接受一个Rational & 类型参数的,现在(在main()里 的第三行)给我一个int,叫我怎么办?
如果我是编译器的话我会认为这种情况下模板参数T就是int,于是实例化下面的函数
const  Rational int >   operator   *  (  const  Rational int >&  lhs,  const  Rational int >&  rhs );
该函数可以通过隐式类型转换接受int类型的参数,顺利完成任务!
可是,编译器很笨,做不到。

于是可以考虑关掉OPTION1,打开OPTION2:把模板非成员函数当成友元+非成员+模板函数。为什么需要友元呢?
OPTION1失败的原因是编译器无法生成合适的模板函数实例,它认为没有合适的实例可以调用,于是编译失败。
如果模板函数变成友元,编译器首先看到有Rational 对象定义出来(main()中第二第三行),就认为会有一个下面的函数 作为友元:
const  Rational int >   operator   *  (  const  Rational int >&  lhs,  const  Rational int >&  rhs );
当然,它不会在编译阶段去计较是不是真的有这么一个函数实例(友元嘛,就是朋友的东东,朋友说有,我就相信有咯~)
于是,编译通过!
可是编译通过后,链接却出了问题:到链接阶段才发现,这个“朋友”的模板函数根本没有实例可以调用!(还是一样的问题, 没有可以接受int参数的版本。朋友也不可靠啊……)
问题就在类外部的友元模板函数仅仅在类中得到了声明(declaration)而没有被定义(definition)。对于模板函数,使用者既需要声明,又需要定义。(比如说 vector v,事实上已经通过制定模板参数完成了模板函数的定义。)

于是终于到了最后一步:关掉OPTION1, OPTION2,打开OPTION3。
无可奈何中我们把友元函数的定义放到了模板类的定义中,相当于把这个“朋友”拉上了船,只要我被定义了,你就一定会被定义。同甘共苦,才算是真的朋友@#¥%
于是编译通过了,因为编译器看到了如下的函数被声明
const  Rational int >   operator   *  (  const  Rational int >&  lhs,  const  Rational int >&  rhs );
于是链接通过了,因为随着Rational 对象的定义,上面的函数也实际被定义了出来,编译器很高兴,结果很完美。

事实上这次的读书笔记记了两次,当我重新看第一次的笔记时竟然不知所云,于是重新看了一次,也修改了第一版的笔记成为 第二版。
看来,模板真的很能搞……

你可能感兴趣的:(读书笔记:Effective C++ 炒冷饭 - Iitem46 参数需要类型转换时为模板定义非成员函数)