模板类也可以有友元函数(后面全部简称友元)。模板类的友元分3类:
往下看之前请先明了类模板,模板类,函数模板,模板函数(参考:http://blog.csdn.net/sunxx1986/article/details/6619144),模板具体化的概念。写得有点长,耐心点儿~
在类模板中将一个常规函数声明为友元:
template T>
class HasFriend
{
...
public:
friend void counts();//non-template friend to all HasFriend instantiations
...
};
上述声明时counts()
函数成为类模板所有实例化(即模板类)的友元。例如,它是HasFriend
,HasFriend
,HasFriend
…的友元。
注意,counts()
函数不是通过模板类的对象来调用的(友元函数并不是成员函数),而且没有对象参数,那它是如何访问HasFriend
的模板类对象呢?有很多种可能性:它可以访问全局对象;可以使用全局指针访问非全局对象;可以创建自己的对象(在函数内部创建HasFriend
对象);可以访问独立于对象的模板类的静态数据成员。
如果要为友元函数提供模板类参数,可以如下所示来进行友元声明吗?
friend void report(HasFriend &);//possible?
答案是不可以。原因是不存在HasFriend
这样的类,它只是一个类模板,而只有具体化的模板类才有对象,如HasFriend
。要提供模板参数的话,必须指明具体化。例如,可以这样做:
template T>
class HasFriend
{
...
public:
friend void report(HasFriend<T>&);//bound template friend
...
};
为理解上述代码,想想声明一个具体化的对象(用模板类声明一个对象)时,将生成具体化:
HasFriend<int> hf;
编译器将用int代替模板参数T,因此友元函数的声明如下:
class HasFriend
{
...
public:
friend void report(HasFriend &);//bound template friend
...
};
也就是说,带HasFriend
参数的report()
将成为HasFriend
模板类的友元。同样,带HasFriend
参数的report()
将成为HasFriend
模板类的友元,这两个函数互为重载函数。
注意:report()
本身并不是模板函数,而只是使用了一个模板参数。这意味着必须要为友元定义显示具体化。
void report(HasFriend &){...};//explicit specialization for int
void report(HasFriend &){...};//explicit specialization for double
下面来看一个程序:
/* program1 */
#include
using std::cout;
using std::endl;
template <typename T>
class HasFriend
{
private:
T item;
static int cnts;
public:
HasFriend(const T& i):item(i){cnts++;}
~HasFriend(){cnts--;}
friend void counts();
friend void report(HasFriend&);//template class parameter
};
//each specialization has its own static data number
template <typename T>
int HasFriend::cnts=0;
//non-template friend to all HasFriend classes
void counts()
{
cout<<"int count"<int>::cnts<<";";
cout<<"double count"<double>::cnts<//non-template friend to the HasFriend class
void report(HasFriend<int> & hf)
{
cout<<"HasFriend:" <//non-template friend to the HasFriend class
void report(HasFriend<double> & hf)
{
cout<<"HasFriend:" <int main()
{
cout<<"No objects declared:";
counts();
HasFriend<int> hfi1(10);
cout<<"After hfi1 declared:";
counts();
HasFriend<int> hfi2(20);
cout<<"After hfi2 declared:";
counts();
HasFriend<double> hfdb(15.5);
cout<<"After hfdb declared:";
counts();
report(hfi1);
report(hfi2);
report(hfdb);
return 0;
}
在codeblock13.12环境种编译之后有条警告信息,如下:
HasFriend
和
HasFriend
)时,需要明确指明具体的模板类(而不是以类模板的形式统一定义)。这也就导致,如果我们的主程序中需要有更多的实例化模板类,而且还需要用到相应的非模板友元,比如,增加使用了
HasFriend
模板类的对象后,需要重新去修改非友元的定义(增加和
HasFriend
相对应的非模板友元)。这样使得程序严重缺乏灵活性。
可以修改上面的示例,使友元本身成为模板函数。具体的说,为约束模板友元做准备,要使类的每一个具体化(每一个模板类)都获得与之匹配的具体化的友元。包含以下3步。
template void counts();
template void reports(T &);
template
class HasFriendTem
{
...
pulic:
friend void counts();
friend void report<>(HasFriendTem &);
...
};
声明中的<>
指出这是函数模板具体化。这些语句根据类模板声明中模板参数进行具体化。也就是说,类中友元声明中,传递给友元的参数TT
和HasFriendTem
其实是具体化模板友元的过程(可以理解为只具体化了一部分,因为TT
是什么类型目前还不知道)。对于report()
,<>
可以为空,因为可以从函数参数推断出函数模板的模板参数类型为HasFriend
。
当然,也可以使用:
friend void report<HasFriendTem<TT>>(HasFriendTem<TT> &);
但是counts()
函数没有参数,因此必须使用模板参数语法()来指明具体化。其中的
TT
是HasFriendTem
类的参数类型。
同样,理解上述声明的最佳方式也是设想声明一个具体化的对象。例如:
HasFriendTem<int> hft;
编译器用int
替换TT
,并生成下面的类定义:
classs HasFriendTem
{
...
public:
friend void counts();
friend void report<>(HasFriendTem &);
...
};
基于TT
的具体化将变为int
,基于HasFriendTem
的具体化将变为HasFriendTem
。因此,函数模板具体化后的counts
和report
被声明为HasFriend
模板类的友元。同理,counts
和report
为HasFriend
模板类的友元…
我们来看一个能同时满足3个要求的程序:
/* program2 */
#include
using std::cout;
using std::endl;
//template prototypes
template <typename T> void counts();
template <typename T> void report(T &);
//template class
template <typename TT>
class HasFriendTem
{
private:
TT item;
static int cnts;
public:
HasFriendTem(const TT & i):item(i){cnts++;}
~HasFriendTem(){cnts--;}
friend void counts();
friend void report<>(HasFriendTem &);
};
template <typename TT>
int HasFriendTem::cnts=0;
//template friend functions definitions
template <typename T>
void counts()
{
cout<<"template size:"<<sizeof(HasFriendTem)<<";";
cout<<"template counts():"<::cnts<template <typename T>
void report(T& hf)
{
cout<int main()
{
counts<int>();
HasFriendTem<int> hfi1(10);
HasFriendTem<int> hfi2(20);
HasFriendTem<double> hfdb(15.5);
report(hfi1);//generate report(HasFriendTem &)
report(hfi2);
report(hfdb);//generate report(HasFriendTem &)
cout<<"counts() output:\n" ;
counts<int>();
cout<<"counts;
counts<double>();
return 0;
}
在codeblock13.12环境种编译运行之后结果如下:
counts
和
counts
报告的模板大小不同,这表明每种
HasFriendTem
模板类都有自己的友元函数。
program1
包含一个
counts()
函数,它是所有
HasFriend
类模板具体化之后的模板类的友元;而
program2
包含两个
counts()
函数(在具体化的时候被定义),即
counts()
和
counts
函数,它们分别是模板类
HasFriendTem
和模板类
HasFriendTem
对应的友元。由于
counts()
函数模板没有参数,所以在调用的时候必须指定具体化类型,但对于
report()
调用,编译器可以通过从参数中推断出要具体化的类型,当然,使用
<>
格式也能取得同样的效果:
reportint>>(hfi1);//same as report(hfi1)
reportdouble>>(hfdb);//same as report(hfdb)
我们前面还有个疑问没有解决,还记得program1
在编译过程中出现的警告吗?我们来回看一下:
<>
语法(注意只有模板友元的声明中函数名字之后才会用
<>
语法)了。
上面的约束模板友元是在类模板外面声明友元模板。通过在类模板内部声明友元模板,可以创建非约束模板友元,即每个函数模板的具体化都是每个类模板具体化的友元。对于非约束模板友元,友元模板的类型参数和类模板的类型参数是不同的:
templateT>
{
...
template friend void show(C &,D &)
...
};
我们直接来看一个例子:
/* program3 */
#include
using std::cout;
using std::endl;
template <typename T>
class ManyFriend
{
private:
T item;
public:
ManyFriend(const T & i):item(i){}
//unbound template friend declation
template <typename C,typename D> friend void show(C &,D &);
};
template <typename C,typename D> void show(C & c,D & d)
{
cout<", "<int main()
{
ManyFriend<int> hfi1(10);
ManyFriend<int> hfi2(20);
ManyFriend<double> hfdb(15.5);
cout<<"hfi1, hfi2: ";
show(hfi1,hfi2);//generate show &,ManyFriend &>(ManyFriend & c,ManyFriend & d)
cout<<"hfdb, hfi2: ";
show(hfdb,hfi2);//generate show &,ManyFriend &>(ManyFriend & c,ManyFriend & d)
return 0;
}
在codeblock13.12环境下编译运行的结果如下:
program3
中,函数调用
show(hfi1,hfi2)
与下面的具体化相匹配:
void showint > &,ManyFriend<int> &>(ManyFriend<int> & c,ManyFriend<int> & d);
它是所有ManyFriend
类模板具体化的友元,所以可以访问所有具体化的模板类对象的item
成员,但是本例中它只访问了ManyFriend
对象的item
成员。
同样,函数调用show(hfdb,hfi2)
与下面的友元具体化匹配:
void generate showdouble > &,ManyFriend<int> &>(ManyFriend<double> & c,ManyFriend<int> & d);
它是所有ManyFriend
类模板具体化的友元,所以可以访问所有具体化的模板类对象的item成员,本例中它访问了ManyFriend
对象的item
成员和ManyFriend
对象的item成员。
小结一下:非约束模板友元指的是每一个具体化的模板类对应所有具体化的友元,每一个具体化的友元也是所有具体化的模板类的友元。
说明:此博文很多内容是参考《C++ Primer Plus(第六版)》这本书上的。看过此书的人,如果够仔细的话,会发现本人在上面关于类模板、模板类、函数模板、模板函数这几个概念的描述上少量地方有些加粗,而这几个部分恰恰是和参考书上描述的不一样。
其实本人一开始看书的时候也很迷惑,以非模板友元那部分内容为例,书中文字上描述说“模板类中将一个常规函数声明为友元”而之后的代码上却是在类模板上声明常规友元(忘记了的可以看一下原书):
template T>
class HasFriend
{
...
public:
friend void counts();//non-template friend to all HasFriend instantiations
...
};
这前后看起来不久矛盾了吗?当时我还以为是译者在翻译上没有注意这个细节问题,于是找了个英文原版,发现两者是一致的。
所以这个问题我们可以这样理解,我们在代码上的确是在类模板中声明常规友元(非模板友元),但是类模板终究是模板,当我们声明对象的时候,编译器才会根据类模板定义对应的模板类,这个时候不就刚刚好是在模板类中声明常规友元吗(只不过声明它的是编译器,而不是我们)。
最后补充一下,本博文统一用的是和代码上相一致的概念描述。