条款46:需要类型转换时请为模板定义非成员函数

条款46:需要类型转换时请为模板定义非成员函数
    Define non-member functions inside templates when type conversion are desired.
   
    还记得在条款24中,我们提到只有non-member函数才有能力'在所有实参身上实施隐式类型转换'.今天我们讨论的
东西与该条款相关,如果现在的你恰好忘记了前面这条款的内容,那么我建议你还是先把那条款在消化一下,再开始
这一款吧,呵呵,'磨刀不误砍柴工'嘛!废话我就不多说了,我们进入正题.
    '只有non-member函数才有能力在所有实参身上实施隐式类型转换',在泛型编程的世界里,这是否也成立呢?下面我
们来稍微修改一下前面这款例子中的代码,然后验证一下是这句话是否依然成立.
    template<typename T>
    class Rational{
    public:
        Rational(const T& numberator = 0, const T& denominator = 1);
        const T numerator()const;
        const T denominator()const;
        ...
    };
    template<typename T>
    const Rational<T> operator*(const Rational<T>& left_handle_side,
                                const Rational<T>& right_handle_side );
    我们希望此时的它能够一往无前的支持混合式算术运算,我沙沙地写出下面两行代码进行测试:
    Rational<int> one_half( 1, 2 );
    Rational<int> result = one_half * 2; //compile error.
    咦?奇怪,难道换成了模板就不行了?事实确实如此!在条款24中编译器能够知道我们在尝试调用哪个函数(就是接
受两个Rationals参数的那个operator*啦),但在这里编译器却不知道我们要调用哪个函数,它们试图想出什么函数被
名为operator*的template具现化出来.但现在的问题是它们没有足够大的能耐来完成如此的工作.为了完成具现化工
作,必须先算出T是什么,于是它们开始了下面的尝试:
    编译器首先看到了operator*的两个参数,它们的类型分别是Rational<int>(one_half类型)和int(2的类型),每个参
数分开考虑.以one_half进行推导比较容易,operator*的第一个参数声明为Rational<T>,而传递给函数的第一实参类
型是Rational<int>,故T一定int.但到了第二个参数推导的时候,问题就来了.operator*声明的第二个参数为
Rational<T>,但实参确实int类型(2).编译器如何推算T?会不会发生像条款24出现的隐式参数转换呢?(编译器适用
Rational<int>的non-explicit构造函数将2转换为Rational<int>,进而将T推导为int),但事实却是很惨酷的:它们没有
那样做.因为在template实参推导过程中从不将隐式类型转换函数纳入考虑.这下麻烦了,那有什么办法能够解决这一
问题的呢?别急,其实我们只需要利用一个事实就可以缓和编译器在template实参推导方面受到的挑战,这个事实就是
:template class内的friend声明式可以指涉某个特定的non-member函数.class templates并不依赖template实参推导
(后者只施行于function templates身上),所以编译器总是能够在class Rational<T>具现化时得知T.因此,我们的问题
的解决方案就出来了:令Rational<T> class声明适当的operator*为其friend函数.
    template<typename T>
    class Rational{
    public:
        ... //同上
        friend const Rational operator* ( const Rational& left_handle_side,
                                          const Rational& right_handle_side );
    };
    template <typename T>
    const Rational<T> operator*(const Rational<T>& left_handle_side,
                                const Rational<T>& right_handle_side )
    {...}
    现在对operator*的混合式调用就可以编译了,因为当对象one_half被声明为Rational<int>,class Rational<int>
就被具现化出来了,而作为过程的一部分,friend函数operator*也就被自动声明出来了,此时后者为一个具现的函数
而不是函数模板罗,因此编译器可在调用它时使用隐式转换,于是混合式调用就基本成功了!基本?难道还有未完成的
工作?呵呵,当你编译的时候没问题,是吧?你试试链接呢?竟然无法连接!!SHIT!怎么个情况????
    现在我们回头来思考这个问题.混合式代码通过了编译是因为编译器知道我们要调用哪个函数,但那个函数只被
声明与Rational内,并没有被定义出来.而我们意图令此class外部的operator* template提供定义式,但是行不通----
----如果我们自己声明了一个函数,就有责任定义那个函数.既然我们没有提供定义式,连接器当然找不到它!
    最简单的可行方法就是将operator*函数本体合并至其声明式内:
    template<typename T>
    class Rational{
    public:
        ... //同上
        friend const Rational operator*(const Rational& left_handle_side,
                                        const Rational& right_handle_side ){
            return Rational( left_handle_side.numerator() * right_handle_side.numerator(),
                             left_handle_side.denominator() * right_handle_side.denominator() );
        }
    };
    简单的Build一下,我们就可以看到,对operator*的混合调用现在可以编译连接并执行了.哦也!operator*定义体放
到了类的内部成了inline函数,而inline声明会给类带来冲击.为了最小化这种冲击,我们可以令operator*不做任何事
情,只调用一个定义于class外部的辅助函数,当然,对本条款中的例子而言,这样做没有太大的意义,因为operator*只
是一个单行函数,但对于更复杂的函数而言,这样做也许就有价值.本款的例子典型长成这样:
    template<typename T> class Rational;
    template<typename T>
    const Rational<T> doMultiply(const Rational<T>& left_handle_side,
                                 const Rational<T>& right_handle_side);
    template<typename T>
    class Rational{
    public:
        ...
        friend const Rational<T> operator*(const Rational<T>& left_handle_side,
                                           const Rational<T>& right_handle_side){
            return doMultiply( left_handle_side, right_handle_side );
        }
        ...
    };
    好了,今天的讨论就到这里!
    请记住:
    ■ 当我们编写一个class template,而它所提供之'于此template相关的'函数支持'所有参数之隐式类型转换'时,请将
那些函数为'class template内部的friend函数'.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值