您的位置:首页 > 编程语言 > C语言/C++

C++之需要类型转换时请为模板定义非成员函数(46)---《Effective C++》

2017-08-15 11:33 429 查看

条款46:需要类型转换时请为模板定义非成员函数

请将这篇文章和条款24对比学习,可以更好地理解这篇文章的主题,条款24主要介绍了“non-member函数才可以对实参进行隐式转换的能力”,具体举例是自定义一个类A,然后让A和一个int类型整数相乘,分别介绍了将operator*函数声明为member函数或者non-member函数,由于乘法满足交换律,member函数无法解决operator*函数的交换问题,而使用non-member函数可以对实参进行隐式类型转换问题,因此也就可以满足交换律问题。那么这篇文章我们具体讲什么问题呢?下面我们先来看看如下代码:

template <typename T>
class Rational{
public:
Rational(const T& numerator=0,const T& 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){
...
}
...
Rational<int> oneHalf(1,2);
Rational<int> result=oneHalf*2;


我们希望支持混合算术运算,所以我们希望上面的代码可以通过编译,和条款24相比只是Rational和operator*如今都变成了templates,但是我们可以发现

这段代码无法通过编译,无法通过编译,无法通过编译!

什么原因呢?直观的可以感觉到Rational的operator*函数的template和non-template版本解决方案不同,下面我们来进行一个分析吧!在template函数中,编译器不知道我们想要调用哪个函数,取而代之的是,它们试图找到哪个函数被名为operator*的template具现化出来,它们知道它们应该应该具现化某个“名为operator*并且接受两个Rational< T>参数的函数”,然而在进行这一步之前,需要先算出T是什么类型,可是编译器并没这个能力,就用上面的代码进行分析,第二个参数是int,怎样进行T的推导,我们无从得知吧!有些同学可能认为可以调用Rational的non-explicit构造函数进行隐式类型转换,然鹅由于template实参推导过程中,不讲隐式类型转换纳入考虑,这样就会导致T类型即使在知晓之后也无法进行类型转换,因此在进行函数具现化之前,T的类型都无法确定,因此无法通过编译。

我们怎样可以解决这个问题呢?

template class内的friend声明式可以涉及某个特定函数,那意味这class Rational<T>可以声明operator*是它的一个friend函数。class templates并不依靠template实参推导(后者只适用于function templates身上),所以编译器总是能够在class Rational<T>具现化时候得知T,因此,令Rational<T>class声明适当的operator*为其friend函数,可以简化整个问题。

1、可以通过编译却无法链接的代码:

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


上述代码也可以写成如下方式:

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


解释一下为什么此时就可以成功了呢?因为当对象oneHalf被声明为一个Rational<int>,于是class Rational<int>就会被具现化出来,而作为过程的一部分,friend函数operator*于是被声明出来,后者是一个函数而非函数模板(function template),因此编译器可以在调用它的时候使用隐式类型转换函数,所以这种方法可以通过编译,然而却无法链接。

为什么无法链接呢?函数被声明于Rational内,并没有被定义出来,我们意图令class 外的operator* template汀定义式,行不通,因为我们没有定义呀!

2、即能通过编译又能通过链接的代码:

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


或者我们也可以通过“friend函数调用外部辅助函数”的方式进行调用:

template <typename T>
const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs);

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


可能doMultiply的实现方式是如下这种方式:

template <typename T>
const Rational<T> doMultiply(const Rational<T>& lhs,const Rational<T>& rhs){
return Rational<T>(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());
}


大家也可以参看我写的一个简单示例进行简单理解:

#include <iostream>
#include <string>
using namespace std;
template <typename T>
class Exp{
public:
Exp(T t){
this->t = t;
}
Exp(Exp<T>& e){
this->t = e.t;
}
Exp& operator=(Exp<T>& e){
this->t = e.t;
return *this;
}
void show(){
cout << t << endl;
}
friend Exp<T> operator*(const Exp<T>& t1, const Exp<T>& t2){
T t3 = t1.t*t2.t;
return Exp(t3);
}
private:
T t;
};
int main(){
Exp<int> t1(10);
Exp<int> t2(100);
t2.show();
Exp<int> t3 = t1*t2;
t3.show();
return 0;
}


运行结果:



总结:

当我们编写一个class template时候,它提供“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template内部的friend函数”。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐