模板类和友元的总结和实例验证

      实际上,在模板类中声明定义友元函数,主要存在三种情况。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,代码如下:

// 文件‘FT.h’,模板类的约束模板友元函数
#include"iostream"
using namespace std;
#define flag
#ifdef flag
template
  
  
   
   
class FriendTem{
	int length;
	static int seq;
	T* data;
public:
	FriendTem(){length=0;data=NULL;seq++;}
    FriendTem(int len,T* da)
	{
		length=len;
		data=new T[len];
		for(int i=0;i
   
   
    
    &);//此处使用了带模板类参数的具体化友元
	friend  void counter();
};
template
    
    
     
     
int FriendTem
     
     
      
      ::seq=0;
ostream&  operator<< (ostream& os,FriendTem
      
      
        & temp) { int len=temp.length; for(int i=0;i 
       
         & temp) { int len=temp.length; for(int i=0;i 
        
          ::seq<<"个'int'对象 "; cout<<"已经定义"< 
          
          
            ::seq<<"个'double'对象"< 
           
             ft1(10,A); counter(); cout<<"int:ft1="< 
            
              <<"\n"; FriendTem 
             
               ft2(5,C); counter(); cout<<"double:ft2="< 
              
                <<"\n"; FriendTem 
               
                 ft3(8,B); counter(); cout<<"int:ft3="< 
                
                  <<"\n"; FriendTem 
                 
                   ft4(6,D); counter(); cout<<"double:ft4="< 
                   
                  
                 
                
               
              
             
            
           
          
         
        
      
     
     
    
    
   
   
  
  

         如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所示:

#include "stdafx.h"
#include "iostream"
using namespace std;
 template
     
     
      
      
 class MF
 {
	 T item;
 public:
	 MF(const T& i):item(i){}
	 template
      
      
       
         friend void show(C&,D&);//此处声明
 };
 template
       
       
        
        //此处定义
 void show(C& c,D& d)
 {cout<
        
        
         
         <<"  "<
         
         
           < 
          
            hf1(10),hf2(8); MF 
           
             hf3(3.56); show(hf1,hf2);show(hf1,hf3); return 0; } 
            
           
         
        
        
       
       
      
      
     
     
         

           总结:使用方法为:"类内声明--模板函数 ,类外定义---模板函数体",无需添加模板类类型参数语句emplate<typename T> (T 为模板类类型参数)。友元关系为 " 多对多 "。但是经过实验验证,此种方法不适合重载输出符”<<“用于模板类实例化对象的输出。编译的时候,会显示错误【error C2593: “operator <<”不明确】。

2.2重载输出运算符的特例

         上段总结的时候已经说明,标准的方法去重载输出运算符”<<“,并能够成功。这里是修改过的可以编译版本。如下所示,注意声明为类内声明,但是又不同于2.1 的类内声明,定义尽管也是类外定义,但也相异于2.1中的类外定义。具体见21-24行的声明,30,38行的定义。
         代码片如下实例3
/"Ft.h",类内定义和声明
#define  flag
#ifdef flag
#include"iostream"
using namespace std;
template
     
     
      
      
class FriendTem{
	int length;
	static int seq;
	T* data;
public:
	FriendTem(){length=0;data=NULL;seq++;}
    FriendTem(int len,T* da)
	{
		length=len;
		data=new T[len];
		for(int i=0;i
      
      
       
       //模板类中使用友元模板函数
	friend  ostream&  operator<< (ostream&,FriendTem
       
       &);
	template
        
        
         
         
	friend  void counter(FriendTem
         
         &); }; template 
          
            int FriendTem 
           
             ::seq=0; template 
            
              ostream& operator<< (ostream& os,FriendTem 
             & temp) { int len=temp.length; for(int i=0;i 
              
                void counter(FriendTem 
               & temp) { cout<<"已经定义"< 
                
                  <<"个对象"< 
                 
                   ft1(10,A); counter(ft1); cout<<"int:ft1="< 
                  
                    <<"\n"; FriendTem 
                   
                     ft2(5,C); counter(ft2); cout<<"double:ft2="< 
                    
                      <<"\n"; FriendTem 
                     
                       ft3(8,B); counter(ft3); cout<<"int:ft3="< 
                      
                        <<"\n"; FriendTem 
                       
                         ft4(6,D); counter(ft4); cout<<"double:ft4="< 
                         
                        
                       
                      
                     
                    
                   
                  
                 
               
             
            
          
        
        
      
      
     
     

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所示:

#include "stdafx.h"
#include "iostream"
using namespace std;
template
         
         
          
           void counts();
template
          
          
           
            void report(T&);
//template
           
           
            
             ostream& operator<< (ostream&,T&);使用同样的方法说明友元为模板类
template
            
            
             
             
class HF{
	TT item;
	static int ct;
public:
	HF(const TT& i):item(i){ct++;}
	~HF(){ct--;}
	friend void counts
             
             (); friend void report<>(HF&); //friend ostream& operator<< <>(ostream&,HF&); }; template 
                
                  int HF 
                 ::ct=0; template 
                  
                    void counts() { cout< 
                    
                    
                      )<<" "; cout< 
                      
                      
                        ::ct< 
                       
                         void report(T&hf ) { cout< 
                        
                          < 
                         
                           ostream& operator<<(ostream& os,T& hf) { os< 
                          
                            <<" "< 
                           
                             (); HF 
                            
                              hf1(10);//cout< 
                             
                               hf2(8); HF 
                              
                                hf3(10.60);//cout< 
                               
                                 (); counts 
                                
                                  (); return 0; } 
                                 
                                
                               
                              
                             
                            
                           
                          
                         
                        
                       
                      
                     
                    
                   
                
            
            
           
           
          
          
         
         

          总结:使用规则为“ 类外声明模板函数,类内使用模板参数语法声明友元关系,类外不带模板类型参数定义模板函数体,使用时候可能显式具体化 ”。但是经过验证,此种方法也不适合重载输出符”<<“用于模板类实例化对象的输出。编译的时候,会显示错误【error C2593: “operator <<”不明确】。由于在模板类类声明友元关系时,使用了模板形参,因此,只有使用同一类型参数的模板函数才和对应的实例模板类才有友元关系,即友元关系为 “一对一”。

 3.2重载输出运算符的特例

          上段已经说明,若使用标准的写法重载输出运算法。倘若要用此种方法去重载输出运算符。怎么办,这里提供改进的写法。实例代码片如下面的实例5所示:

#include "stdafx.h"
#include"iostream"
using namespace std;
template
            
            
             
              class FriendTem;
template
             
             
              
               ostream&  operator<< (ostream&, FriendTem
              
              
               
                &);//注意此处的声明
template
               
               
                
                 void counter(FriendTem
                
                
                  &); template 
                 
                   class FriendTem{ int length; static int seq; T* data; public: FriendTem(){length=0;data=NULL;seq++;} FriendTem(int len,T* da) { length=len; data=new T[len]; for(int i=0;i 
                  
                    (ostream&,FriendTem 
                   
                     &);//注意此处的声明 friend void counter <>(FriendTem 
                    
                      &); }; template 
                     
                       int FriendTem 
                      
                        ::seq=0; template 
                       
                         ostream& operator<< (ostream& os, FriendTem 
                        
                          & temp)//注意此处的定义 { int len=temp.length; for(int i=0;i 
                         
                           void counter(FriendTem 
                          
                            & temp) { cout<<"已经定义"< 
                           
                             <<"个对象"< 
                            
                              ft1(10,A); counter(ft1); cout<<"int:ft1="< 
                             
                               <<"\n"; FriendTem 
                              
                                ft2(5,C); counter(ft2); cout<<"double:ft2="< 
                               
                                 <<"\n"; FriendTem 
                                
                                  ft3(8,B); counter(ft3); cout<<"int:ft3="< 
                                 
                                   <<"\n"; FriendTem 
                                  
                                    ft4(6,D); counter(ft4); cout<<"double:ft4="< 
                                    
                                   
                                  
                                 
                                
                               
                              
                             
                            
                           
                          
                         
                        
                       
                      
                     
                    
                   
                  
                
               
               
              
              
             
             
            
            

             注意请看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码所示

//"FT.h"
#define flag
#ifdef flag
#include"iostream"
using namespace std;
template
              
              
               
               
class FriendTem{
	int length;
	static int seq;
	T* data;
public:
	FriendTem(){length=0;data=NULL;seq++;}
    FriendTem(int len,T* da)
	{
		length=len;
		data=new T[len];
		for(int i=0;i
               
               
                
                & temp)
		{
		int len=temp.length;
		for(int i=0;i
                
                
                 
                 & temp)
		{ 
	       cout<<"已经定义"<
                 
                 
                  
                  <<"个对象"<
                  
                  
                    int FriendTem 
                   
                     ::seq=0; #endif //主函数 #include "stdafx.h" #include"iostream" #include "FT.h" using namespace std; int _tmain(int argc, _TCHAR* argv[]) { int A[10]={0,1,2,3,4,5,6,7,8,9}; int B[8]={1,2,3,4,5,6,7,8}; double C[5]={1.23,2.31,5.008,7.89,0.014}; double D[6]={0.11,0.22,0.33,0.44,0.55,0.66}; FriendTem 
                    
                      ft1(10,A); counter(ft1); cout<<"int:ft1="< 
                     
                       <<"\n"; FriendTem 
                      
                        ft2(5,C); counter(ft2); cout<<"double:ft2="< 
                       
                         <<"\n"; FriendTem 
                        
                          ft3(8,B); counter(ft3); cout<<"int:ft3="< 
                         
                           <<"\n"; FriendTem 
                          
                            ft4(6,D); counter(ft4); cout<<"double:ft4="< 
                            
                           
                          
                         
                        
                       
                      
                     
                    
                  
                 
                 
                
                
               
               
              
              
        

4.2 模板类作为返回类型和参数类型在友元函数中的体现 

             代码片来自点击打开链接。在作者给出的代码片中,出现了如下图所示的声明,将模板类作为友元函数返回类型和友元函数参数类型的写法,我认为,这种写法十分有趣和有效。并且调用的时候也不需要具体化使用。






  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值