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

friend是C++中封装的漏网之鱼。

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

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

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

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


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

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

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


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


这对C++模板类同样适用。


template
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
const Rational operator*(const Rational &lhs, const Rational &rhs)
{
    return Rational( lhs.get_numerator()*rhs.get_numerator(), lhs.get_denominator()*rhs.get_denominator());
}

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


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


Rational a = Rational(1,2);

Rational ret = a * 2; 


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


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


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


下面一一进行分析:

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


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

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


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


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


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

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


最最核心的一段话:

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


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


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

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

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


于是,正确的Rational模板类的定义为:


#include 

using namespace std;

template
class Rational
{
    friend
    const Rational operator*(const Rational &lhs, const Rational &rhs)
    {
        return Rational( 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 onehalf(1,2);
    Rational oneThird(1,3);
    Rational 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 关键字 对于模板 并不仅仅只是友元!!!_第1张图片


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


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

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


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

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

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

 friend!!!!将函数的声明与定义均置于类内部。



你可能感兴趣的:(C++,Primer)