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