1、最简单的友员形式
#include <iostream>
using namespace std;
template <typename T> /* 声明模板类 */
class A
{
T num;
friend void show_size(); /* 指定友员 */
};
A<double> a_global1;
A<int> a_global2;
A<char> a_global3;
void show_size()
{
cout << sizeof(a_global1.num) << endl;
cout << sizeof(a_global2.num) << endl;
cout << sizeof(a_global3.num) << endl;
}
int main()
{
show_size(); /* 输出:8、4、1 */
}
以上代码中show_size
作为A
的友员,无论是参数还是返回值,都与具体化的A
的类型没有关系。
根据测试代码也可以看出,show_size
中可以直接接触到具体化的A<double>
、A<int>
和A<char>
的num
成员。
所以说,show_size
是所有具体化A
的友员
但是,由于接口太过简单,所以这种友员函数的用处并不是很大,从上面的测试代码也可以看出,它只能引用一些已经存在的(全局)对象。
2、无约束的模板友员
通常情况下我们使用友元函数机制,目的是为了能够可控地突破private和protect权限限制,自由地从class的外部操作class的成员。因此,通常class的友员函数都会有至少1个参数的类型为该class的类型。比如说,我们可能需要一个这样的友员:
friend void show_size(A<xxx> &a);
综上,为了实现将模板类作为友员函数的参数,可以通过实现一种「无约束的模板友员」来实现:
首先,我们需要像实现一个普通的模板函数一样实现一个友员函数:
template <typename K>
void show_size(K &a)
{
cout << sizeof(a.num) << endl;
}
我们的意图很明显——我们想用具体化的(比如A<double>
)代替类型K
。从而可以用sizeof
获取到实例a
的num
成员的大小。
其次,也是友员函数的核心,要让A
向show_size
授予friend权限:
template <typename T> /* 声明模板类 */
class A
{
T num;
template <typename K>
friend void show_size(K &a); /* 指定友员 */
};
其实,这与第1节中的方式没什么区别,只不过由于这里的show_size
是模板函数,故在类中声明该函数为友员时,需要加上模板声明:template <typename K>
完整的代码如下
#include <iostream>
using namespace std;
template <typename T> /* 声明模板类 */
class A
{
T num;
template <typename K>
friend void show_size(K &a); /* 指定友员 */
};
template <typename K>
void show_size(K &a)
{
cout << sizeof(a.num) << endl;
}
int main()
{
A<int> aint;
A<double> adouble;
A<char> achar;
show_size(aint); /* 输出:4 */
show_size(adouble); /* 输出:8 */
show_size(achar); /* 输出:1 */
}
以上的实现方法,本质上来说就是将一个模板函数声明为了友元函数。也没什么特别的。
这种友员的实现方式存在一个问题——友员函数被所有具体化的A
共享。
假设有如下全局变量:
A<double> a_global1;
A<int> a_global2;
A<char> a_global3;
则show_size
的实现可以是这样的:
template <typename K>
void show_size(K &a)
{
cout << sizeof(a.num) << " "
<< sizeof(a_global1.num) << " "
<< sizeof(a_global2.num) << " "
<< sizeof(a_global3.num) << endl;
}
相对于第1节最普通的友元函数,无约束的友员函数只不过是提供了可以将模板类作为参数传入的解决方案。
即对于任何具体化的A
来说,show_size
都是它的友员。这种自由有时候可能并不是我们希望的。
另外还有一点需要注意的是,show_size
作为一个模板函数,其具体化动作是在调用它的时候进行的,如果代码中指示创建了A
对象,没有调用show_size
,则编译器不会生成show_size
的实际代码。这一点上与下一节即将讨论的有约束友元函数存在一些比较微妙的差别。
3、有约束的友员函数
鉴于前2中有友元函数拥有了太过开放的权限。
现在,我希望让友员函数内部只能接受1种具体化的A
。比如说,对于A<double>
的友员函数,只能将A<double>
的private和protect成员公布给它,A<int>
、A<char>
等具体化的A
并不将其当做友员。
通过「有约束」的友元函数可以实现这种约束。
友元函数的定义形式不需要改:
template <typename K>
void show_size(K &a)
{
cout << sizeof(a.num) << endl;
}
在A
中授予friend权限的方式需要调整:
template <typename T> /* 声明模板类 */
class A
{
T num;
friend void show_size<>(A<T> &a); /* 指定友员 & 模板具体化 */
};
上述代码中,需要注意的是show_size
后面的<>
,这个是在提示「正在进行模板具体化」。相当于将上面show_size
的定义中的类型K
替换成了A<T>
。
完整的代码为:
#include <iostream>
using namespace std;
template <typename K>
void show_size(K &);
template <typename T> /* 声明模板类 */
class A
{
T num;
friend void show_size<>(A<T> &a); /* 指定友员 */
};
template <typename K>
void show_size(K &a)
{
cout << sizeof(a.num) << endl;
}
int main()
{
A<int> aint;
A<double> adouble;
A<char> achar;
show_size(aint); /* 输出:4 */
show_size(achar); /* 输出:1 */
show_size(adouble); /* 输出:8 */
}
此时,如果我们补充了3个全局变量:
A<double> a_global1;
A<int> a_global2;
A<char> a_global3;
并尝试在友员函数中使用:
template <typename K>
void show_size(K &a)
{
cout << sizeof(a.num) << " "
<< sizeof(a_global1.num) << " "
<< sizeof(a_global2.num) << " "
<< sizeof(a_global3.num) << endl;
}
将会报错。正如前面所说的那样,show_size
是A
的「有约束」友元函数,在其中除了类型K
对应的具体化的A
以外,都不认为它是友员函数,自然也就不能接触到它们的private成员。
我们可以再进一步地做个实验。
首先将友元函数改一下:
template <typename K>
void show_size(K &a)
{
cout << sizeof(a.num) << " "
<< sizeof(a_global3.num) << endl;
}
注意,其中a_global3
的类型为A<char>
。
然后去掉2个测试case:
int main()
{
A<int> aint;
A<double> adouble;
A<char> achar;
// show_size(aint);
show_size(achar); /* 输出:1 1 */
// show_size(adouble);
}
此时可以正常编译并运行,完整的代码为:
#include <iostream>
using namespace std;
template <typename K>
void show_size(K &);
template <typename T> /* 声明模板类 */
class A
{
T num;
friend void show_size<>(A<T> &a); /* 指定友员 & 模板具体化 */
};
A<double> a_global1;
A<int> a_global2;
A<char> a_global3;
template <typename K>
void show_size(K &a)
{
cout << sizeof(a.num) << " "
<< sizeof(a_global3.num) << endl;
}
int main()
{
A<int> aint;
A<double> adouble;
A<char> achar;
// show_size(aint);
show_size(achar); /* 输出:1 1 */
// show_size(adouble);
}
注意,此时的模板函数show_size
的具体化工作,在A
被具体化时,就进行了。也就是说,每当有1个类型的A
被具体化时,编译器就会对应地具体化一个show_size
,并将二者关联为友员的关系。