一个由《程序员面试宝典》引出的问题。
描述模板类的友元重载,用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;
}