类模板与友元函数
C++中的类可以声明友元函数和友元类,同样类模板也可以声明友元函数和友元类,在类模板中声明友元函数有三种情况:非模板友元函数、约束模板友元函数和非约束模板友元函数,接下来,将针对这三种友元函数进行详细的讲解。
1、非模板友元函数
非模板友元就是在类模板中声明普通的友元函数~~和友元类~~,例如,在一个类模板中声明一个友元函数,代码如下所示:
template<typename T>
class A
{
T t;
public:
friend void func();
};
在类模板A中声明了一个普通的友元函数func(),则func()函数是类模板A所有实例的友元函数,它可以访问全局对象,也可以使用全局指针访问非全局对象;可以创建自己的对象,也可以访问独立于对象的模板的静态数据成员。
当然,也可以为类模板的友元函数提供模板类参数,其示例代码如下:
template<typename T>
class A
{
T t;
public:
friend void show(const A<T>& a);
};
在上述代码中,show()函数并不是模板函数,而只是使用一个模板作参数,这就要求在使用友元函数时必须要显式具体化,指明友元函数要引用的参数的类型,例如:
void show(const A<int>& a);
void show(const A<double>& a);
也就是说模板形参为int类型的show()函数是A<int>类的友元函数,模板形参为double类型的show()函数是
A<double>类的友元函数。
为了让读者加深理解,接下来通过一个案例来演示这两种友元函数的用法,具体如例1所示。
例1
1 #include <iostream>
2 using namespace std;
3 template<typename T>
4 class A
5 {
6 T item;
7 static int count; //静态变量
8 public:
9 A(const T& t) :item(t){ count++; }
10 ~A(){ count--; }
11 friend void func(); //无参友元函数
12 friend void show(const A<T>& a); //有参友元函数
13 };
14 template<typename T>
15 int A<T>::count = 0; //初始化静态变量
16
17 void func() //func()函数实现
18 {
19 cout << "int count: " << A<int>::count << ";";
20 cout << "double count: " << A<double>::count << ";" << endl;
21 }
22 void show(const A<int>& a) //模板形参为int类型
23 {
24 cout <<"int: "<< a.item << endl;
25 }
26
27 void show(const A<double>& a)
28 {
29 cout << "double: " << a.item << endl;
30 }
31 int main()
32 {
33 func(); //调用无参友元函数
34 A<int> a(10); //创建int类型对象
35 func();
36 A<double> b(1.2);
37 show(a); //调用有参数的友元函数
38 show(b);
39 system("pause");
40 return 0;
41 }
运行结果如图1所示。
图1 例1运行结果
由图1可知,第一次调用func()函数时,int类型的类对象与double类型的类对象都为0个,因此count值均为0。代码34行创建了一个int类型的类对象a,则再调用func()函数,int类型的对象为1,double类型的对象仍为0;接着代码36行又创建了一个double类型的对象b,然后代码37-38行分别以a和b作为参数调用show()函数,则分别输出了int与double类型的数据10和1.2。
注意:在调用有模板形参的友元函数时,要对友元函数显式具体化,它们是各自相同类型的对象的友元函数。
2、约束模板友元函数
约束模板友元函数本身就是一个模板,但它的实例化类型取决于类被实例化时的类型(被约束),每个类的实例化都会产自一个与之匹配的具体化的友元函数。我们以例5-6为例,修改类A中的两个友元函数为模板友元函数。这个过程主要包含三个步骤。
(1)在类定义的前面声明函数模板。
template<typename T>
void func();
template<typename T>
void show(T& t);
(2)在类模板中将函数模板声明为类的友元函数。
template<typename U>
class A
{
……
friend void func<U>();
friend void show<>(const A<U>& a);
}
上述友元函数的声明中,函数名后的<>指出函数模板要实例化的类型,它是由类模板的参数类型决定。例如,如果定义一个类模板对象,A<int> a,则编译器会生成下面的类定义:
class A
{
……
friend void func<int>();
friend void show<>(const A<int>& a);
}
类中友元函数模板会根据类的实例化类型而实例化出与之匹配的具体函数,但要注意,类模板的实例化不会实例化一个友元函数,只是声明友元而不实例化,只有在调用时,函数才会实例化。
上述友元函数声明中,show()函数有类的引用作为参数,可以从函数参数推断出模板类型参数,所以其函数名后的<>可以为空。
(3)为友元函数模板提供定义。
为函数模板提供定义,必须在类内声明,类外定义,例如:
template<typename T>
void func(){cout<<A<T>::成员<<endl;}
template<typename T>
void show(T& t){cout<<t.成员<<endl;}
接下来为了更好的理解与掌握应用,我们将例5-6中的两个函数修改成函数模板并声明为定义类的友元,具体如例2所示。
例2
1 #include <iostream>
2 using namespace std;
3 //函数模板声明
4 template<typename T>
5 void func();
6 template<typename T>
7 void show(T& t);
8 //类模板定义
9 template<typename U>
10 class A
11 {
12 private:
13 U item;
14 static int count;
15 public:
16 A(const U& u) :item(u){ count++; }
17 ~A(){ count--; }
18 friend void func<U>(); //友元函数模板
19 friend void show<>(A<U>& a); //友元函数模板
20 };
21 template<typename T>
22 int A<T>::count = 0;
23 //友元函数模板的定义
24 template<typename T>
25 void func()
26 {
27 cout << "template size: " << sizeof(A<T>) << ";";
28 cout << " template func(): " << A<T>::count << endl;
29 }
30 template<typename T>
31 void show(T& t)
32 {
33 cout << t.item << endl;
34 }
35
36 int main()
37 {
38 func<int>(); //调用int类型的函数模板实例,int类型,其大小为4字节
39 A<int> a(10); //定义类对象
40 A<int> b(20);
41 A<double> c(1.2);
42 show(a); //调用show()函数,输出类对象的数据成员值
43 show(b);
44 show(c);
45 cout << "func<int> output:\n";
46 func<int>(); //运行到此,已经创建了两个int类型对象
47 cout << "func<double>() output:\n";
48 func<double>();
49
50 system("pause");
51 return 0;
52 }
运行结果如图2所示。
图2 例2运行结果
在例2中,将func()与show()函数定义成了模板并声明为类的友元,在定义函数模板时是在类外定义的。当调用函数时,func()函数后带有<>说明函数的实例化类型,而show()是直接调用的。
3、非约束模板友元函数
通过在类内部声明友元函数模板,可以创建非约束友元函数,即函数模板是每个类实例的友元,函数的模板形参不受类模板形参的影响,如下面的定义所示:
template<typename T>
class A
{
//........
template<typename T, typename U>
friend void show(T& t, U& u);
};
对于非约束友元,友元模板的参数与类模板的类型参数是不同的。在类内声明函数模板,在类外定义,代码如下所示:
template<typename T, typename U>
void show(T& t, U& u){……}
它是类模板每一个实例的友元,因此能够访问所有实例的类成员,我们可以通过一个案例来演示其用法,具体如例3所示。
例3
1 #include <iostream>
2 using namespace std;
3 template<typename T>
4 class A
5 {
6 private:
7 T item;
8 public:
9 A(const T& t) :item(t){}
10 template<typename U, typename V> //在类内部声明函数模板
11 friend void show(U& u, V& v);
12 };
13 template<typename U, typename V>
14 void show(U& u, V& v)
15 {
16 cout << u.item << "," << v.item << endl;
17 }
18 int main()
19 {
20 A<int> a(10);
21 A<int> b(20);
22 A<double> c(1.2);
23 cout << "a,b: ";
24 show(a, b);
25 cout << "a,c: ";
26 show(a, c);
27 system("pause");
28 return 0;
29 }
运行结果如图3所示。
图3 例3运行结果
函数模板的形参类型与类模板的形参类型不相关,因此它可以接受任何类型的参数,例5-8中,第一次调用传入的是两个int类型的类对象,第二次调用传入的是一个int类型和一个double类型的类对象。