读书笔记_Effective_C++_条款四十六:须要类型转换时请为模板定义非成员函数

这个条款能够当作是条款24的续集,咱们先简单回顾一下条款24,它说了为何相似于operator *这样的重载运算符要定义成非成员函数(是为了保证混合乘法2*SomeRational或者SomeRational*2均可以经过编译,2不能同时进行隐式类型转换成某个Rational,再做this用)。函数

因此咱们通常将之定义成友元函数,像下面这样:this

 1 class Rational
 2 {
 3 private:
 4     int numerator;
 5     int denominator;
 6 public:
 7     Rational(int n = 0, int d = 1): numerator(n), denominator(d){assert(denominator != 0);}
 8     int GetNumerator() const{return numerator;}
 9     int GetDenominator() const {return denominator;}
10     friend const Rational operator* (const Rational& r1, const Rational& r2);
11 };
12 const Rational operator* (const Rational& r1, const Rational& r2)
13 {
14     return Rational(r1.numerator * r2.numerator, r1.denominator * r2.denominator);
15 }

如今咱们来引入模板,能够像下面这样写,注意这里的operator*是一个独立的模板函数:spa

 1 template <class T>
 2 class Rational
 3 {
 4 private:
 5     T Numerator;
 6     T Denominator;
 7 
 8 public:
 9     Rational(const T& Num = 0, const T& Den = 1) : Numerator(Num), Denominator(Den){}
10     const T GetNumerator() const
11     {
12         return Numerator;
13     }
14 
15     const T GetDenominator() const
16     {
17         return Denominator;
18     }
19 
20     string ToString() const
21     {
22         stringstream ss;
23         ss << Numerator << "/" << Denominator;
24         return ss.str();
25     }
26 };
27 
28 template <class T>
29 const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
30 {
31     return Rational<T>(a.GetNumerator() * b.GetNumerator(), 
32         a.GetDenominator() * b.GetDenominator() );
33 }

但下面main函数的两行却都不能经过编译:code

1 int main()
2 {
3     Rational<int> a(3, 5);
4     Rational<int> c = a * 2; // 不能经过编译!
5     c = 2 * a;               // 不能经过编译!
6     cout << c.ToString() << endl;
7 }

缘由是编译器推导T出现了困难,a * 2在编译器看来,能够由a是Rational<int>将T推导成int,可是2是什么,理想状况下编译器会尝试将它先转换成一个Rational<int>,并将T推导成int,但事实上编译器在“T推导过程当中从不将隐式类型转换函数归入考虑”。因此不管是a * 2仍是2 * a都是不能经过编译的,一句话,隐式转换+推导T不能被同时被编译器接受。blog

解决问题的思路便接着产生,编译器既然不能同时接受这两个过程,就让它们事先知足好一个条件,再由编译器执行另外一个过程好了。ip

若是把这个operator*放在template class里面,也就是先在生成模板类的那一步就定下T,这样编译器只要执行隐式转换这一步就能够了。编译器

所以咱们能够这样来改:string

 1 template <class T>
 2 class Rational
 3 {
 4  5     friend Rational operator* (const Rational& a, const Rational& b);  6 };
 7 
 8 template <class T>
 9 const Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
10 {
11     // 这里友元函数的声明并非用来访问类的私有成员的,而是用来进行事先类型推导的
12     return Rational<T>(a.GetNumerator() * b.GetNumerator(), 
13         a.GetDenominator() * b.GetDenominator() );
14 }

注意红色部分,咱们添加了一个友元函数的声明,果真编译经过了,但连接时又报错了,缘由是连接器找不到operator*的定义,这里又要说模板类中的一个特殊状况了,它不一样与普通的类,模板类的友元函数只能在类中实现,因此要把函数体部分移至到类内,像下面这样:io

 1 template <class T>
 2 class Rational
 3 {
 4  5     friend Rational operator* (const Rational& a, const Rational& b)
 6     {
 7         return Rational (a.GetNumerator() * b.GetNumerator(),
 8             a.GetDenominator() * b.GetDenominator());
 9     }
10 11 }

这下编译和连接都没有问题了。这里还要说一下,就是移至类内后,T的标识符能够不写了,但若是非要写成下面这样,天然也是OK的。编译

1 friend Rational<T> operator* (const Rational<T>& a, const Rational<T>& b)
2 {
3     return Rational<T>(a.GetNumerator() * b.GetNumerator(),
4         a.GetDenominator() * b.GetDenominator());
5 }

operator*里面只有一句话,但若是friend函数里面的东西太多了,能够定义一个辅助方法,好比DoMultiply(),这个DoMultiply能够放在类外去实现,DoMultiply自己不支持混合乘法(2 * SomeRational或者SomeRational * 2),但因为在operator*里面已经进行了隐式类型转换,因此到DoMultiply这一级是没有问题的。

 

最后总结一下:

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

相关文章
相关标签/搜索