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