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

    1. 条款24举出一个Rational的例子,来说明为什么只有non-member函数才有能力"在所有实参身上实施隐式类型转换".Rational的定义如下:

    class Rational{
    public:
        Rational(int numerator=0,int denominator=1);
        int numerator()const;
        int denominator()const;
    private:
        int numerator;
        int denominator;
    };
    View Code

    operator*声明为Rational的non-member函数:

    const Rational operator*(const Rational& lhs,const Rational& rhs);

    在未涉及到模板时,这么做是正确的,如果将Rational升级为类模板,可能像这样:

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

        那么对于以下代码:

    Rational<int> oneHalf(1,2);
    Rational<int> result=onHalf*2;

        看起来"onHalf*2"似乎应该使函数模板operator*具现化并调用它,但实际上编译不通过,根本原因在于编译器"在template实参推到过程中从不将隐式转换纳入考虑":转换函数在函数调用过程中的确被使用(如果operator*是一个函数而不是函数模板的话),但在调用一个函数之前,必须要知道那个函数存在,而为了知道它,必须先为相关的function template推导出参数类型(然后才可将适当的函数具现化出来).然而template实参推导过程中并不考虑采纳"通过构造函数而发生的"隐式类型转换!

    2. 只要利用一个事实就可以改变这种现状:template class内的friend声明式可以指涉该特定函数.也就是说Rational可以声明operator*为它的一个friend函数,class template并不依赖于template实参推导,因此编译器总是能够在class Rational具现化时得知T,因此,令Rational<T> class声明适当的operator*为其friend函数可简化整个问题:

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

        现在对operator*的调用就可以通过编译了,发生的改变是:oneHalf被声明时,class Rational<int>被具现化出来,而作为过程的一部分,friend函数operator*也就被作为一个函数而非函数模板自动声明出来,因此编译器可以在调用它时使用隐式转换函数.

        此时还未结束,以上代码虽然通过了编译,但却无法链接——Rational<int> operator*(const Rational<int>&lhs,const Rational<int>&rhs)已经被声明出来,但却没有定义.使用template是行不通的,因为此当Rational被具现化时,operator*只是作为一个普通的函数声明被具现化出来,编译器不会认为它和operator*函数模板有关联而为它具现化出一个函数实体.

        解决办法就是将operator*函数本体合并至其声明式内:

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

        此时对operator*的调用可编译链接并执行.

    3. 在本条款中,friend的作用不再是提升函数或类的访问权限,而是使类型转换发生在所有参数身上:为了使该函数被自动具现化,需要把它声明在类内部,而在类内部声明non-member函数的唯一办法就是令它成为一个friend.

        由于operator*需要在Rational内部定义,它被默认声明为inline,为了使这种inline声明带来的冲击最小(本例中operator*已经是一个单行函数,但更复杂的函数也许需要这样),可以使它调用一个定义域class外部的辅助函数,由该辅助函数完成实际功能:

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

        许多编译器为了实行template具现化,要求把template定义式放在头文件内,因此可能需要在头文件内定义doMultiply,但doMultiply可以不为inline.

  • 相关阅读:
    常用python机器学习库总结
    链接器link.exe 编译器cl.exe 资源编译器rc.exe
    LRESULT与wParam和lParam的问题
    CreateDialog和DialogBox
    如何通俗易懂地解释卷积?
    深度学习在graph上的使用
    一文弄懂神经网络中的反向传播法——BackPropagation
    WM_COMMAND消息
    win32编程中消息循环和WndProc()窗口过程函数
    使用UEditor 的时候,ajax注意使用同步的方法
  • 原文地址:https://www.cnblogs.com/reasno/p/4802040.html
Copyright © 2011-2022 走看看