zoukankan      html  css  js  c++  java
  • effective C++ 条款 46:需要类型转换时请为模板定义非成员函数

    条款24说过为什么惟有non-member函数才有能力“在所有实参身上实施隐式类型转换”。本条款将Rational和operator*模板化:

    template<typename T>
    class Rational{
        Rational(const T& number = 0,
            const T& denominator = 1);
        const T number() const;
        const T denominator() const;
    };
    template<typename T>
    const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs)
    {......}

    像条款24一样,我们希望支持混合算术运算,我们希望一下代码顺利通过编译:

    Rational<int> oneHalf(1,2);
    Rational<int> result = oneHalf * 2;//错误!无法通过编译

    条款24内,编译器知道我们尝试调用什么函数(就是接受两个Rationals参数的那个operator*),但这里编译器不知道我们想要调用哪个函数。取而代之的是,他们试图想出什么函数被名为operator*的template具现化(产生)出来。它们知道应该可以具现化某个“名为operator*并接受两个Rational<T>参数”的函数,但完成这一具现化行动,必须先算出T是什么。问题是它们没这个能耐。

    为了推导T,它们看了看operator*调用动作中的参数类型。每个参数分开考虑。

    以oneHalf进行推导,过程并不困难。T一定是int。第二个参数被声明是Rational<T>,但传递给operator*的第二个实参(2)类型是int。编译器如何根据这个推算出T?你或许会期盼编译器使用Rational<int>的non-explicit构造函数将2转换成Rational<int>,进而将T推导成int。但它们不那么做,因为在template实参推导过程中从不将隐式类型转换函数纳入考虑。绝不!这样的转换在函数调用过程中的却被使用,但在能够调用一个函数之前,首先必须知道那个函数存在。为了知道它,必须先为相关的function template推导出参数类型(然后才可将适当的函数具现化出来)。template实参推导过程中并不考虑采纳“通过构造函数而发生的”隐式类型转换

    有方法可以缓和编译器在template实参推导方面受到的挑战:template class内的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);
        //声明operator*函数
    };
    template<typename T>
    const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs)
    {......}

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

    在一个class template中,template名称可被用来作为“template和其参数”的简略表达方式,所以在Rational<T>内,我们可以只写Rational而不必写Ratioan<T>。和如下声明一样:

    template<typename T>
    class Rational{
    public:
        friend
            const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs);    //声明operator*函数
    };

    然而使用简略表达方式(速记式)比较轻松也比较普遍。

    这个函数只被声明于Rational内,并没有被定义出来。我们意图令此class外部的operator* template提供定义式但是行不通。我们在class Rational template内声明了一个函数,就有责任定义那个函数。

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

    template<typename T>
    class Rational{
    public:
        friend
            const Rational operator* (const Rational& lhs, const Rational& rhs);    //声明operator*函数
        {
            return Rational(lhs.numerator() * rhs.numerator(),
                                    lhs.denominator() * rhs.denominator());

        }
    };

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

    定义于class内的函数都暗自成为inline包括operator*这样的friend函数。你可以将这样的inline声明所带来的冲击最小化,做法是令operator*不做任何事情,只调用一个定义于class外部的辅助函数。Rational是个template意味着这个辅助函数通常也是一个template。Rational头文件代码,很典型的长这个样子:

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

    template<typename T>
    class Rational{
    public:
        ...
    friend
            const Rational<T> operator* (const Rational<T>& lhs, const Rational<T>& rhs);
        {
            return doMultiply(lhs, rhs);        //令friend调用helper
        }
    };

    许多编译器实质上会强迫你把所有template定义式放进头文件内,所以你或许需要在头文件内定义doMultiply(条款30,这样的templates不需非得是inline不可),看起来像这样:

    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*支持混合式操作!

  • 相关阅读:
    AIBigKaldi(二)| Kaldi的I/O机制(源码解析)
    OfficialKaldi(十四)| 从命令行角度来看Kaldi的 I / O
    GNU Make函数、变量、指令
    C/C++编码规范(google)
    [English]precede, be preceded by
    视频压缩技术、I帧、P帧、B帧
    SMB
    printf占位符
    使用 Yocto Project 构建自定义嵌入式 Linux 发行版
    gcc fpic fPIC
  • 原文地址:https://www.cnblogs.com/lidan/p/2355071.html
Copyright © 2011-2022 走看看