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

文章介绍了如何在模板类Rational中处理混合式运算,通过将operator*函数声明为类的friend并将其定义移至类内,解决了编译器在推导template实参时的挑战,确保了隐式类型转换的正确执行。
摘要由CSDN通过智能技术生成

目录

1.前言

2.实例分析

3.总结


1.前言

条款24讨论过为什么只有non-member函数才有能力“在所有实参身上实施隐式类型转换”(条款24:若所有参数都需类型转换,请为此采用non-member函数-CSDN博客),该条款并以Rational class的operator*函数为例。而本条款的改变在于将Rational和operator*模板化了:

template<typename T>
class Rational{

    public:
        Rational(const T& numerator=0,const T& denominator=1);
        const T numperator() const;
        const T denominator() const;
        ....
};
template<typename T>
const Rational<T> operator* (const Rational<T>& lhs,const Rational<T>& rhs)
{
    ...
}

2.实例分析

就像条款24,我们也希望支持混合式(Mixed-mode)算数运算,所以我们希望以下代码顺利通过编译,唯一不同的时Rationl和operator*如今都成了templates:

Rational<int> oneHalf(1,2);//该例子来自条款24,唯一不同的是Rational改为template
Rational<int> result=oneHalf*2;//错误,无法通过编译

上述失败给我们的启示是模板化的Rational内的某些东西和其non-template版本不同。在条款24内,编译器知道我们尝试调用什么函数(即接受两个Rationals参数的那个operator),但这里编译器不知道我们想要调用哪个函数。取而代之的是它们试图想出什么函数被名为operator*的template具现化出来。它们知道他们应该具现化某个“名为operator*”并接受两个Rational<T>参数”的函数,但为完成这一具现化行动,必须先算出T是什么。

为了推导T,它们看了看operator*调用动作中的实参类型。本例中那些类型分别为Ratioanl<int>(oneHalf的类型)和int(的类型)。

以oneHalf进行推导,operator*的第一参数被声明为Rational<T>,而传递给operator*的第一实参(oneHalf)的类型是Rational<T>,所以T一定是int,其它参数的推倒则没有这么顺利。operator*的第二参数被声明为Rational<T>,但传递给operator*的第二实参(2)类型是int。

只要利用一个事实,我们就可以缓和编译器在template实参推导方面受到的挑战:template class内的friend声明式可以指涉某个特定函数。哪意味着class Rational<T>可以声明operator*是它的一个friend函数。class template并不依赖template实参推导(后者只施行于function template身上),所以编译器总是能够在class Rational<T>具现化时得知T,因此,令Rational<T> class声明适当的operator*为其friend函数,可简化问题:

template<typename T>
class Rational{

    public:
        ...
        friend 
        const Rational operator*(const Rational& lhs,const Rational& rhs);
};

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

现在对operator*混合式的调用可以通过编译了,因为当对象oneHalf被声明为一个Rational<int>,class Rational<int>于是被具现化出来,而作为过程的一部分,friend函数operator*(接受Rational<int>参数)也就被自动声明出来。后者身为一个函数而非函数模板(function template),因此编译器可在调用它时使用隐式转换函数(例如Rational的non-exolicit构造函数),而这便是混合式调用之所以成功的原因。

虽然这段代码通过了编译,但是却无法连接。首先我们对Rational内声明的operator*语法进行讨论。

在一个class template内,template名称可被用来作为"template和其参数"的简略表达式,所以在Rational<T>内我们可以只写Rational而不必写Rational<T>。本例中的operator*被声明为接受并返回Rational,如果它被声明如下,一样有效:

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

然而使用简略的表达式比较轻松,也比较普遍。

现在回头想想出现的问题。混合式代码通过了编译,因为编译器知道我们要调用哪个函数(就是接受一个Rational<int>以及又一个Rational<int>的那个operator*),但那个函数只被声明于Rational内,并没有被定义出来,我们意图令此class外部的operator* template提供定义式,但是行不通-如果我们自己声明了一个函数(即Rational template内的作为),就有责任定义那个函数。既然我们没有提供定义式,连接器自然找不到它们。

或许最简单的办法就是将operator*函数本体合并至其声明式内:

template<typename T>
class Rational{

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

    }
}

这便如同我们所期望地正常运作起来:对operator*地混合式调用现在可编译连接并执行。

这项技术地有个有趣点是:虽然使用了friend,却与friend地传统用途“访问class的non-public成分”毫不相干,为了让类型转换可能发生于所有实参上面,我们需要一个non-member函数:为了令这个函数被自动具现化,我们需要将它声明在class内部;而在class内部声明non-member函数的唯一办法就是:令它成为一个friend。

正如条款30所说(条款30:透彻了解inlining的里里外外-CSDN博客),定义于class内的函数都暗自成为Inline,包括像operator*这样的friend函数,可以将这样的inline声明所带来的冲击最小化,做法是令operator*不做任何事情,只调用一个定义于class外部的辅助函数。在本条款的例子中,这样做的意义不大,因为operator*已经是个单行函数,但对更复杂的函数而言,那么做也许就有价值。“令friend函数调用辅助函数”的做法的确值得研究。

“Rational是个template”这一事实意味着上述的辅助函数通常也是个template,所以定义lRational的头文件代码,比如:

template<typename T> class Rational;//声明Rational template
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs);
template<typename T>
class Rational{

    public:
        ...
    friend         
        const Rational<T> operator*(const Rational<T>& lhs,const Rational<T>& rhs)
        {
            return  doMultiply(lhs,rhs);
        }
        ...
};

许多编译器实质上会强迫你把所有template定义式放进头文件内,所以你或许需要在头文件内定义doMultiu\ply,比如以下例子:

template<typename T>
const Rational<T> domultiply(const Rational<T>& lhs,const Rational<T>& rhs)
{

    return Rational<T>(lhs.numerator*rhs.numerator(),lhs.denominator()*rhs.denominator());
}

作为一个template,doMultiply不支持所以混合式乘法,但它其实不需要。它只被operator*调用,而operator*支持了混合式操作,本质上operator*支持了类型转换所需的任何东西,确保两个Rational对象能够相乘,然后它将这两个对象传给一个适当的doMultiply template具现体,完成实际的乘法操作。

3.总结

当我们编写一个class template,而它所提供之“与此template相关的”函数支持“所以参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值