Note of Justin

关于工作和读书的笔记

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  47 Posts :: 0 Stories :: 45 Comments :: 0 Trackbacks

留言簿(14)

搜索

  •  

积分与排名

  • 积分 - 51886
  • 排名 - 433

最新评论

阅读排行榜

评论排行榜

[原创文章欢迎转载,但请保留作者信息]
Justin 于 2010-05-01



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

现在把24课的内容升级为模板,就有了下面的代码:
template typename T>
class Rational {
   
public:
      Rational(
const T& num = 0const 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 RationalT> operator * (const RationalT>& lhs, const RationalT>& rhs)
{
   
return RationalT> (lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
#endif

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

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

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

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

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

posted on 2010-05-01 11:53 Justin.H 阅读(1687) 评论(0)  编辑 收藏 引用 所属分类: Effective C++ 炒冷饭

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理