一个由《程序员面试宝典》引出的问题。
描述模板类的友元重载,用C++代码实现?
这实际上考察的是下面几个问题:
1.模板类的编写
2.模板类中友元函数的编写
3.什么时候会用到友元重载?答案是各种C++中的运算符。最典型的就是输出操作符<<了。
书上给出的答案如下:
#include <iostream> using namespace std; template<class T> class Test; template<class T> ostream & operator<<(ostream & out,const Test<T> &obj); template<class T> class Test{ private: int num; public: Test(int n=0){num=n;} Test(const Test <T> ©){num=copy.num;} //注意在“<<”后加上“<>”表示这是一个函数模板 friend ostream& operator<< <> (ostream & out,const Test<T> &obj); }; template<class T> ostream& operator<<(ostream & out,const Test<T> &obj){ out<<obj.num; return out; } int main(){ Test<int> t(2); cout<<t; return 0; }
只是对上面注释的哪一行不是很理解于是翻了下《C++ Primer》,发现书上对这个问题讲的很详细了。复制过来:
类模板中的友元声明
在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体的友元关系:
(1)普通非模板或函数的友元声明,将友元关系授予明确指定的类或函数
(2)类模板或函数模板的友元声明,授予对有缘所有实例的访问权
(2)只授予对类模板或函数模板的特定实例的访问权的友元声明
1.普通友元
非模板类或非模板函数可以是类模板的友元:
template<class Type> class Bar{ //授权给普通类和或函数 friend class FolBar; friend void fcn(); };
这个声明是说,FolBar的成员和fcn函数可以访问Bar类的任何实例的privete成员和protected成员。
2.一般模板友元
友元可以是类模板或函数模板:
template<class Type> class Bar{ //授权给Foo1或temp1_fcn1的任何实例 template<class T> friend class Foo1; template<class T> friend void temp1_fcn1(const T&); };
这些友元声明使用与类本身不同的类型形参,该类型形参指的是Foo1和temp1_fcn1的类型形参。在这两种情况下,都将没有数目限制的类和函数设为Bar的友元。Foo1的友元声明是说,Foo1的任何实例都可以访问Bar的任何实例的私有成员,类似地,temp1_fcn1的任何实例可以访问Bar的任意实例。
这个友元声明在Bar与其友元Foo1和temp1_fcn1的每个实例之间建立了一对多的映射。对Bar的每个实例而言,Foo1或temp1_fcn1的所有实例都是友元。
3.特定的模板友元关系
除了将一个模板的所有实例设为友元,类也可以只授予对特定实例的访问权。
template<class T> class Foo2; template<class T> void temp1_fcn2(const T&); template<class Type> class Bar{ //只授权给参数类型为char*的实例 friend class Foo2<char *>; friend void temp1_fcn2<char *>(char * const &); };
即使Foo2本身是类模板,友元关系也只扩展到Foo2的形参类型为char*的特定实例。类似地,temp1_fcn2的友元声明是说,只有参数类型为char*的函数实例是Bar类的友元。形参类型为char*的Foo2和temp1_fcn2的特定实例可以访问Bar的每个实例。
下面形式的友元声明更加常见:
template<class T> class Foo3; template<class T> void temp1_fcn3(const T&); template<class Type> class Bar{ //Bar的每一个实例只能访问参数类型和Bar相同的Foo3和temp1_fcn3的实例 friend class Foo3<Type>; friend void temp1_fcn3<Type>(const Type &); };
这些友元定义了Bar的特定实例与使用同一模板实参的Foo3或temp1_fcn3的实例之间的友元关系,每个Bar实例有一个相关的Foo3和temp1_fcn3友元:
Bar<int> b1;//它的友元是Foo3<int>和temp1_fcn3<int> Bar<string> bs;//它的友元是Foo3<string>和temp1_fcn3<string>
只有与给定Bar实例有相同模板实参的那些Foo3或temp1_fcn3版本是友元。因此,Foo3<int>可以访问Bar<int>的私有部分,但不能访问Bar<string>或者任意其他Bar实例的私有部分。
4.声明依赖性
当授予给定模板的所有实例的访问权的时候,在作用域中不需要存在该类模板或函数模板的声明。实质上,编译器将友元声明也当做类或函数的声明对待。
想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数:
template <class T> class A; template <class T> class B{ public: friend class A<T>;//ok,A做了声明 friend class C;//ok,C是一个普通类 template<class S> friend class D;//ok,D是一个模板,并且是对D的所有实例授权 friend class E<T>;//error,需要声明 friend class F<int>;//error,需要声明 };
在g++中会出现这个错误:
如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。
上面就是《C++ Primer》关于模板类中友元的说明,理解完这些后再来看这道题就很好理解。根据第3点,特定的模板友元关系可以明白为什么需要<>了,实际上是<T>,不过可以只写成<>。
根据第4点类型依赖性可以明白为什么在最上面需要加上声明了。
然后可以按照第2点一般模板友元关系的方式进行改写,代码如下:但是感觉没有这个必要。
注意,此时前面的声明不需要了,因为按照上面第4点,对所有实例都访问权时是不需要事先声明的。
<<后面也不需要<>了。因为此时前面加上了template<class TT>这个关键字。
注意:为了防止以后在类模板或函数模板中漏掉<>这个符号,可以这样记忆,对于类模板或函数模板,要么有template<class >修饰,要么有需要有<>两者必须有其一
#include <iostream> using namespace std; template<class T> class Test{ private: int num; public: Test(int n=0){num=n;} Test(const Test <T> ©){num=copy.num;} //注意在“<<”后加上“<>”表示这是一个函数模板 template<class TT> friend ostream& operator<< (ostream & out,const Test<TT> &obj); }; template<class T> ostream& operator<<(ostream & out,const Test<T> &obj){ out<<obj.num; return out; } int main(){ Test<int> t(2); cout<<t; return 0; }