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

friend 关键字 对于模板 并不仅仅只是友元!!!

2016-08-05 14:50 369 查看
friend是C++中封装的漏网之鱼。

C++中的friend允许其他的类或者是函数访问本类的任何成员,甚至是private成员,只要该类声明其为友元。

可是,在有些情况下,并不是允许外界访问类的内部实现而使用友元

这就是在 “模板定义” 与 “隐式类型转换” 之间的一个trick了。

首先,看一个简单的有理数的模板类,该类定义了有理数,并且实现有理数的乘法。

注:下述代码中,将operator*声明为非成员函数,是因为 

“如果你需要对成员函数所有的参数(所有的,当然也就包括this指针啦)进行类型转换,那么将该函数声明为非成员函数”。

也就是说,只有当参数位于参数列的时候,这个参数才是隐式类型装换的合格参与者。

updated:这里的参数列指的就是函数的形参列表!!!但是类的成员函数的那个隐式参数(即this指针)不是隐式转换的合格参与者!!!

这对C++模板类同样适用。
template<typename T>
class Rational
{
private:
int numerator;
int denominator;

public:
Rational(const T & _x = 0, const T & _y=1):numerator(_x),denominator(_y){}

const T get_numerator() const{return numerator;}
const T get_denominator() const {return denominator;}
};

template<class T>
const Rational<T> operator*(const Rational<T> &lhs, const Rational<T> &rhs)
{
return Rational<T>( lhs.get_numerator()*rhs.get_numerator(), lhs.get_denominator()*rhs.get_denominator());
}


在上述代码中,重载了 * 符号,用于计算两个有理数之间的乘法。

一个自然而然的道理,如果我们需要支持 有理数 * 自然数 ,这是一个无可厚非的要求。当我们才有下面的调用 

Rational<int> a = Rational(1,2);

Rational<int> ret = a * 2; 

在非模板类中,该函数会将2进行隐式类型转换为Rational对象。再进行乘积运算。

糟糕!编译不通过,在非模板类中这是很正常的事,可是在模板类中却出现了问题.

简言之,编译器陷入了“纠结”的境地!!!!

下面一一进行分析:

当编译器看到operator*的调用的时候,编译器不知道我们想要调用什么函数,因为编译器看到这个模板函数时,第一要做的就是将函数实例化出来,也就是要首先推断出T的类型但是几经周折,发现不行。

首先,为了推导出T, 编译器对 * 调用的两个参数进行入手,分别为 Rational<int>   和 int, 由第一个参数可以很容易的知道得到 T 为int, 但是在第二个实参呢,编译器怎么推断 T 的类型?? 你也许会说,此时编译器就应该使用
Rational<int> 的隐式构造函数 啊。不就可以 推导出 T 的类型了吗。 

但是,编译器绝不会这么做,因为在 模板实参 的推导过程当中是不会 考虑隐式转换的

这是本文最重要的一句话。

因此,在面对这样的实参推导 的问题是,friend 便出场了,由于 friend
可以在  模板类 中指明某个特定的函数
,也就是说,在函数调用之前,声明该函数,那么在函数调用时,相应的类模板 就不再需要 依赖于
模板实参的推导了,而只需要对这个友元函数进行参数推导即可。



因此将operator * 声明为该类的友元之后,编译器的行为便不一样了。

类模板并不依赖于实参的推导(此时operator*函数只是该类的一个模板友元函数),因为此时的实参推导只施行于该友元模板函数身上,所以编译器总是能够在Rational类实例化的时候得知T。

最最核心的一段话:

在onehalf 对象被定义的时候,Rational函数就被实例化了,相应的,它的友元函数  operator* 也就被实例化出来了,也就是说,此时的operator
*
 不再是一个模板函数了,那么 onehalf  *  2 的时候,便是调用这个已经被实例化的函数了。于是乎,隐式类型转换便可以使用与参数推导了。

但是,还有一个问题,便是链接时的问题了,此时,编译器知道我们要调用的是哪个函数了,但是现在那个函数只是被声明在Rational类中,并没有实现它。 如果我们在Rational外部定义该函数,这是行不通的。

因此,只在类定义体中声明该函数,如果不定义的话,连接器便会发出抱怨,找不到定义体。

(updated:这里该函数的定义必须由类定义负责,否则该函数就必须是现在类的外面,那么自然而然该函数就必须得是模板函数,那么参数推导又不起作用了!!!因此必须定义在函数的内部!!!)

因此,将 operator* 声明为友元函数并且将实现定义在类中。

于是,正确的Rational模板类的定义为:
#include <iostream>

using namespace std;

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

private:
int numerator;
int denominator;

public:
Rational(const T & _x = 0, const T & _y=1):numerator(_x),denominator(_y){}

const T get_numerator() const{return numerator;}
const T get_denominator() const {return denominator;}
};

int main()
{
Rational<int> onehalf(1,2);
Rational<int> oneThird(1,3);
Rational<int> ret = onehalf*oneThird;
cout << ret.get_denominator() << " " << ret.get_numerator() << endl;
ret = onehalf*2;
cout << ret.get_denominator() << " " << ret.get_numerator() << endl;
ret = 2*onehalf;
cout << ret.get_denominator() << " " << ret.get_numerator() << endl;
return 0;
}


运行结果为:



大功告成!!上述代码成功的实现了我们的功能!!!!

于是,在类模板定义时,出现了同样的话,如果  在编写一个类模板的时候,而 该类的与模板相关的函数  需要支持函数参数

隐式转换的时候,将该函数定义为模板类类中的friend函数。

updated:模板函数对于参数的类型推导是绝对不会考虑 “构造函数的隐式类型转换的”!!这与一般的函数调用是不一样的,因此我们如果需要对函数的参数进行类型推导,那么就需要将该函数定义为非模板类型,这是编译器就会陷入两难的境地:

然而,对于一般的函数(非模板函数),编译器是会进行参数推导的(包括调用non-explicit构造函数)!!!

  1在参数推导时使用隐式转换  2 为了让这个函数被具现化,我们又需要将它声明在模板类的内部!!  

 friend!!!!将函数的声明与定义均置于类内部。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息