模板类和友元的总结和实例验证
实际上,在模板类中声明定义友元函数,主要存在三种情况。C++ Primer 和 C++ Primer Plus两本书均有总结。但是,由于总结的侧重点不同,导致看起来稍显晦涩。本文试图将其组织总结。另外本外总结了在上述提到的两本书中,尚未归类的方法。秉承实用主义原则,将其列在第四点。同时,由于重载输出运算符的特殊性,特别对此在三种情况下的使用做了总结和归纳并给出代码。
1.普通友元(C++ Primer)或模板类的非模板友元函数(C++ Primer Plus)
在模板类中声明类或非模板函数的友元声明。下面详细解释将非模板函数声明为模板类之友元函数的两种类型:
1.1非模板友元函数&不带模板类参数
如下图的函数func(),其对模板类HF的所有实例化如HF<int>,HF<string>均是友元函数:
通俗来说,即一种友元函数声明,对该模板类的所有实例化对象均具有友元关系(一对多),这类友元函数可以访问模板类任意实例的privated或者protected成员。由于func()函数不是通过对象调用的,没有对象参数。如果要访问HF对象,可以访问全局对象;或者通过全局指针访问非全局对象;或者访问独立于对象的模板类的静态数据成员(实例1函数counter()即为此功能)。
1.2非模板友元函数&带模板类参数
倘若要为此类友元函数提供模板类参数,如何进行友元声明。以下图的函数func(HT<T> &)为例,要提供模板类参数对友元函数必须具体化。由于不存在HF这样的对象,第4行不能声明为friend void func(HF&),必须有特定的具体化,如HF<string>。如下图所示:
基于此种声明,若主函数声明特定类型的对象 HF<double> hf1, 则带HF<double>参数的func(),将成为HF<double>的友元。同样其重载版本带HF<int>参数的func(),将成为HF<int>的友元,由于func非模板函数,因此必须为要使用的(或者可能使用的)友元定义显示具体化。在上面的例子,倘若定义了HF<double> hf1,HF<int> hf2,要使用友元函数fun(),必须在类外,同时显示具体化int和double的函数体【void func(HF<double>&){......}】和【void func(HF<int>&){......}】。显然,此时为限定模板类型参数(比如以下的实例仅仅针对 int和 double)的“一对一”友元关系:
具体见实例1,代码如下:
如21行的友元声明和26行33行的友元定义。函数完成对输出运算符“<<”的重载。函数counter()访问独立于对象的模板类的静态数据成员,记录对应某个类型的对象数目。
总结:在模板类中定义带模板类参数的非模板友元函数。方法简单化为” 模板类内--声明非模板函数,模板类外--显式具体化定义非模板函数体“,具有" 一对一 "的友元关系。在模板类外显式具体化定义非模板函数体时,无需添加模板类类型参数语句------template<typename T>(T 为模板类类型参数) ”。 此种方法,“适用于对输出运算符的重载<<”,用于输出模板类实例化对象(有限的,即已经显式具体化的类型)。
2.一般模板友元关系(C++ Primer)或模板类非约束模板友元函数(C++ Primer Plus)
2.1通用声明、定义和使用
使用模板类本身不同的类型形参,在类内将友元函数声明为模板函数。以下面的定义为例,func(T& )的友元声明表示任意实例的func() 都可以访问bar的任意实例,即每一个实例化的func都和模板类bar的所有实例具有友元,即“1对多”,换言之,对于任意一个实例化的bar对象,所有实例化的func()都是其友元。也即此时,模板友元函数和模板类的友元关系是”多对多 “。
在C++primer plus第6版程序清单14.24中,存在类似如下类内声明:
类外定义如下,同样注意定义的时候没有包括模板类型参数也就是template<typename T>:
假设已有实例化 MF<int> 型对象 hf1, hf2 和 MF<double> 型变量 hf3。当使用s how(hf1,hf2) 和 show(hf1,hf3) 时,模板友元函数show()匹配如下,注意原书可能有错误(原书的匹配形式写作 show<MF<int>&,MF<int>&>(.......),应该错误)。此处已经更正。
详细实验代码如实例2所示:
总结:使用方法为:"类内声明--模板函数 ,类外定义---模板函数体",无需添加模板类类型参数语句emplate<typename T> (T 为模板类类型参数)。友元关系为 " 多对多 "。但是经过实验验证,此种方法不适合重载输出符”<<“用于模板类实例化对象的输出。编译的时候,会显示错误【error C2593: “operator <<”不明确】。
2.2重载输出运算符的特例
3.特定的模板友元关系(C++ Primer)或模板类约束模板友元函数(C++ Primer Plus)
3.1普遍情况
在C++ Primer Plus中,声明,定义,使用包括如下的四个步骤。首先,在模板类类声明前声明模板函数;其次,在模板类中声明友元关系---分两种情况,(1)不带模板类型参数的模板函数,如下面的函数counts(),在函数名后添加模板参数语法”<TT>"(TT为模板类类型参数)具体化,对于(2)带模板类型参数的函数,如下面的函数report(),在函数名后省略添加"<>"即可。再次,对模板函数进行定义。模板类类外定义的时候,无需添加模板类类型参数语句emplate<typename TT>。最后,使用的时候,对于第二步的第一种情况函数,调用应具体化,如counts<int>(),第二步的第二种情况函数则直接使用即可。详细如下:
首先,在类声明的前面声明每个友元函数为模板函数:
其次,在类中将其声明为友元函数,注意此时的声明方式的类型标示符及其使用。声明中的<>或者<type>使得模板函数根据类模板参数类型声明具体化。对于函数report(),<>可以为空,因为函数参数能够推断出匹配的函数类型模型,即<>不为空显示的那样。而counts()函数没有参数,因此必须使用<type>具体化。如下所示:
假设声明了这样一个对象:FT<int> squack; 则编译器将用int替换TT,并生成下面的类定义:于是,模板具体化counts<int>()和report<HF<int> >()被声明为HF<int>类的友元。
再次,为友元函数在模板类外提供模板函数定义。
该方式为类外定义。如以下代码的使用的就是这种方法,程序中两个counts()函数分别是某个被实例化的模板类型友元。因为counts()函数调用没有可被编译器用来推断所具有具体化的函数参数,所以应该具体化调用counts<int>();counts<double>();但对于report()调用,编译器可以从参数类型中推断出要使用的具体化。当然,使用"<>"也能有同样的效果。
详细代码如下实例4所示:
总结:使用规则为“ 类外声明模板函数,类内使用模板参数语法声明友元关系,类外不带模板类型参数定义模板函数体,使用时候可能显式具体化 ”。但是经过验证,此种方法也不适合重载输出符”<<“用于模板类实例化对象的输出。编译的时候,会显示错误【error C2593: “operator <<”不明确】。由于在模板类类声明友元关系时,使用了模板形参,因此,只有使用同一类型参数的模板函数才和对应的实例模板类才有友元关系,即友元关系为 “一对一”。
3.2重载输出运算符的特例
上段已经说明,若使用标准的写法重载输出运算法。倘若要用此种方法去重载输出运算符。怎么办,这里提供改进的写法。实例代码片如下面的实例5所示:
注意请看4.5.6.22.23行的声明方法。将其声明为函数模板的方法不一样。类似于使用了嵌套模板的方法,即将模板用作函数模板参数。同样定义也有别于一般的模板函数,具体见30,37行。这里的方法优点类似于2.2所示的方法。似乎是将模板函数声明由模板类外,稍作改变移动了模板类内。而模板类外定义基本不变。运用此种方法重载输出输出运算符应该注意两点:第一点函数名后模板参数语法”<>“必不可少,其实<>有两重意思,一是,表明此友元函数是函数模板;二是,此模板使用的模板类型参数为当前模板类的类型参数class T;第二点,template<class T>究竟哪里应该出现,哪里不应该出现。此处参考自:点击打开链接.
倘若在类中声明友元关系时,不仅使用模板参数语法,而且将模板参数类型(如int ,char*)实例化,又会怎么样呢,见下面的
3.3一般情况
某种程度来说,这种情况是情况【二模板类和模板函数友元关系呈“多对多”的特例】,即模板类将友元关系只授予特定模板函数实例。即,只有特定的实例对类的private和ptotected数据有访问权。如下:
在上述声明和定义中,只有模板形参类型为char*的函数实例才是Bar的友元,即类型为char*的func()能访问Bar的每个实例,这也是“一对多”的友元关系。注意,此处的“一对多”与情况一中【非模板友元函数&不带模板类参数】的“一对多 ”相异,最基本的,所声明的友元函数一种是模板函数,一种是非模板函数。
4.其他模板类和友元关系类型
4.1 类内声明和类内定义带类型参数的非模板函数
直接在类内声明和定义,注意这种声明和定义的方式区别于第一种情况的第二类方式的类内声明,类外具体化(包含 float,int)定义,特别是针对重载输出运算符"<<"的重载。显然,这里没有具体化定义,同时声明的时候,这里也没有”<>“,因“<>”表示友元函数是模板函数,事实上,友元函数随着模板类实例化而实例化(inline)。如下面的实例所示,此种方法能用于重载输出运算符,而且避免了具体化定义函数体。某种程度上,是不限定模板参数的”一对一“的友元关系。
使用此种方法能够重载输出运算符"<<"。同样,输出静态成员的函数counter()能运行成功。但是,这里存在一个缺陷。此种方法,不能适用于使用不带模板类型参数,即如同实例1的counts()一类的函数,来显示全局变量。相反,应该使用如实例4中counter()函数那样带类型参数的函数,则能够。
具体定义和使用方法如下面的实例6代码所示
4.2 模板类作为返回类型和参数类型在友元函数中的体现
代码片来自点击打开链接。在作者给出的代码片中,出现了如下图所示的声明,将模板类作为友元函数返回类型和友元函数参数类型的写法,我认为,这种写法十分有趣和有效。并且调用的时候也不需要具体化使用。