云枫的菜园子

带着一个流浪的心,慢慢沉淀。 https://github.com/CloudFeng

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  15 Posts :: 0 Stories :: 6 Comments :: 0 Trackbacks

   看这个章节的时候又跑回去看了一下 条款 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。这里所写都是自己的读书的时候梳理做的笔记罢了。希望对他人有用,那是我的荣幸。

posted on 2015-04-19 11:40 CloudFeng 阅读(...) 评论(...) 编辑 收藏