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

       看这个章节的时候又跑回去看了一下 条款 24,本章的例子就是基于下面这个例子而来的。在 item 24 中,支持混合运算的示例代码如下:

     1 class Rational {
     2        public:
     3            Rational(int numerator = 0, int denominator = 1);
     4            int mumerator() const;
     5            int denominator() const;
     6        private:
     7            int numerator;
     8            int denominator;
     9    };
    10    const Rational operator* (const Rational& lhs, const Rational& rhs) const
    11    {
    12            return Rational(lhs.mumerator() * rhs.mumerator(), 
    13                        lhs.denominator() * rhs.denominator());
    14    }
    15    Rational oneFourth(1, 4);
    16    Rational result;
    17    result = oneFourth * 2;                    // ok
    18    result = 2 * oneFourth;                    // ok

     item 24 中的结束语是这么写的:

      如果需要为某个函数的所有参数,包括被 this 指针所指的那个隐喻参数,进行类型转换,那么这个函数必须是个 non-member. 

    记住上面这句话,本章节就是在此基础上进行的扩展。


       现在开始进入正题了,现在将它们模板化,代码如下:

     1 template <typename T>
     2   class Rational {
     3       public:
     4            Rational(const T& numerator = 0, const T& denominator = 1);
     5            const T mumerator() const;
     6            const T denominator() const;
     7            //...
     8        private:
     9            T numerator;
    10            T denominator;
    11   };
    12 
    13   template<typename T>
    14   const Rational<T operator* (const Rational<T& lhs, const Rational<T& rhs) const
    15    {
    16            return Rational(lhs.mumerator() * rhs.mumerator(), 
    17                        lhs.denominator() * rhs.denominator());
    18    }
    19 
    20    Rational<int> oneHalf(1, 2);
    21    Rational<int> result = oneHalf * 2;     // 错误!无法通过编译
    问题分析:
         编译器知道我们尝试调用什么函数,但这里编译器不知道我们想要调用哪个函数。它会尽它所能做些事情:它试图想什么函数被名为 operator* 的 template 具现化(产生)出来(参数推导)。
         为了推导 T,它会看 operator* 调用动作汇总的实参类型,每个参数分开考虑。但是一定要切记:template 实参推导过程中从不将隐式类型转换寒素纳入考虑。绝不!
         具体来说,函数调用过程中的确是可以使用隐式转换,但是在能够调用一个函数之前,首先必须知道那个函数存在(函数的签名信息)。而为了知道它,必须先为相关的 function template 推导出参数类型,然后才可将适当的函数具现化出来。然而 template 实参推导过程中不考虑 “通过构造函数而发生的”隐式类型转换。就本例而言,在看到 2 (int 类型) 时,它不能使用 non-explicit 构造函数将 2 转换为Rational<int>,进而将 T 推导为 int。

        那么怎么解决这个问题,使用 template class 内的 friend 声明式可以指涉某个特定函数。因为 class template 不依赖 template 实参推导(只施行于 function templates 身上),所以编译其总能够在 class Rational<T>具现化时得知 T。 因此可以将 operator* 声明为 friend 函数。代码如下:

     1 /* 这段代码可以通过编译,但是不能连接 */
     2   template <typename T>
     3   class Rational {
     4       // (1)
     5       friend const Rational operator*(const Rational& lhs, const Rational& rhs);
     6       // friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs); 等价的
     7       public:
     8            Rational(const T& numerator = 0, const T& denominator = 1);
     9            const T mumerator() const;
    10            const T denominator() const;
    11            //...
    12        private:
    13            T numerator;
    14            T denominator;
    15   };
    16 
    17   // (2)
    18   template<typename T>
    19   const Rational<T operator* (const Rational<T>& lhs, const Rational<T>& rhs) const
    20    {
    21            return Rational(lhs.mumerator() * rhs.mumerator(), 
    22                        lhs.denominator() * rhs.denominator());
    23    } 
    24    Rational<int> oneHalf(1, 2);
    25    Rational<int> result = oneHalf * 2;     // ok! :) 通过编译,但是无法连接 :(

    分析原因:
        混合式代码通过了编译,因为编译器知道要调用哪个函数(1),但(1)函数只被声明 Rational 内,并没有被定义出来。我们意图令此 class 外部的 operator * template 提供定义式,但是行不通。如果我们自己声明了一个函数,就有责任定义那个函数。 既然我们没有提供定义式,连接器当然找不到它!


    解决方法有两个:
    (1)将 operator* 函数体合并至其申明式内

     1 template <typename T>
     2   class Rational {
     3       // (1)
     4       friend const Rational operator*(const Rational& lhs, const Rational& rhs)
     5       {
     6           return Rational(lhs.mumerator() * rhs.mumerator(),
     7                           lhs.denominator() * rhs.denominator());
     8       }
     9       // friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs); 等价的
    10       public:
    11            Rational(const T& numerator = 0, const T& denominator = 1);
    12            const T mumerator() const;
    13            const T denominator() const;
    14            //...
    15        private:
    16            T numerator;
    17            T denominator;
    18   };

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


    (2) 定义于 class 内的函数都暗自成为 inline,包括像 operator* 这样的 friend 函数。为了将这样的inline 声明所带来的冲击最小化,做法是 operator* 啥都不干,只调用一个定义于 class 外部的辅助函数。

     1 // 外部的辅助函数
     2 template<typename T>
     3 const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
     4 {
     5     return Rational<T>(lhs.mumerator() * rhs.mumerator(),
     6                           lhs.denominator() * rhs.denominator());
     7 }
     8 template <typename T>
     9 class Rational {
    10     // (1)
    11     friend const Rational operator*(const Rational& lhs, const Rational& rhs)
    12     {
    13         return doMultiply(lhs, rhs);
    14     }
    15     // friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs); 等价的
    16     public:
    17         Rational(const T& numerator = 0, const T& denominator = 1);
    18         const T mumerator() const;
    19         const T denominator() const;
    20         //...
    21     private:
    22         T numerator;
    23         T denominator;
    24 };

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


    声明:全文文字都是来源《Effective C++》 3th。这里所写都是自己的读书的时候梳理做的笔记罢了。希望对他人有用,那是我的荣幸。

  • 相关阅读:
    jmeter_逻辑控制器
    Mysql-10 存储过程
    Mysql-9 视图
    NAS性能测试
    win系统定时任务设置
    服务端监控有哪些客户端链接了服务
    centos8 添加端口号
    centos8下安装gitlab服务
    【Unity】Galgame视觉小说游戏 其脚本解释器的一种实现
    【个人向】ctf比赛出的一道逆向游戏题——GameTime题解
  • 原文地址:https://www.cnblogs.com/cloudfeng/p/4438845.html
Copyright © 2011-2022 走看看