模板类友元函数
模板类的友元函数
参考:/dreamer_lhs/article/details/53580088
区分:友元是否为函数模板
非模板友元约束(bound)模板友元,友元类型取决于模板类被实例化的类型,一个实例化模板函数非约束(unbound)模板友元,友元函数模板类型不同于类模板类型,一个通用型函数模板
非模板友元
友元函数不是一个模板函数
template<typename T>class HasFriend{...public:friend void counts(); // 对于所有的HasFriend对象的**非模板友元**friend void report(HasFriend<T> &); // 给非模板友元绑定参数};void counts(){... }void report(HasFriend<int>& t){// 显示化模板类型,此时就是HasFriend<int>模板类的友元}
friend void counts()
可能是HasFriend<int>
,HasFriend<double>
… 的友元,也就是面向所有HasFriend
具体对象的友元
counts
访问模板类对象的方式可能有:访问全局对象;使用全局指针访问非全局对象;自己内部创建对象;访问静态数据成员
friend void report(HasFriend<T>& t)
其本身并不是模板函数,而是使用了一个模板参数,意味着友元函数的定义必须要显式化模板类型
[缺点]:非模板友元若是想要绑定参数,涉及到某些模板类的具体化,就必须显示化模板类型,这让程序严重缺乏灵活性
约束模板友元
友元函数是一个模板函数的实例化,且实例化的类型与类模板类型相关
// **step1.** 先在类定义的前面声明每个**函数模板**template<typename T>class HasFriend;template<typename T>void b_counts();template<typename T>void b_report(T&); // 函数模板// **step2.** 类中将**模板函数**声明为友元template<typename T>class HasFriend{...public:friend void b_counts<T> ();friend void b_report<> (HasFriend<T>&); // <>指出这里是b_report函数模板显式**实例化**声明// friend void b_report<HasFriend<T> > (HasFriend<T>&) 也可以这样// 等同于 template void b_report<HasFriend<T> > (HasFriend<T> &);// 将会使用b_report(TT&) 模板生成一个HasFriend<T> 类型的实例(函数定义)};// **step3.** 为友元提供模板定义template<typename T>void b_counts(){... }template<typename T>void b_report(HasFriend<T>& t) // 函数模板**具体化**,其内部可以与b_report(T &)的定义不同{cout << t.data; // 这里会有智能提示}/*** 也可以这样写,那么b_report<HasFriend<T> > (HasFriend<T>&)就是从这个函数模板实例化而来* template<typename T>* void b_report(T& t)* {*cout << t.data* }*/
friend void b_report<> (HasFriend<T>& t )
声明中的<>
指出这是函数模板的实例化。<>
可以为空,因为可以从函数参数推断出函数模板的模板参数类型为HasFriend<T>
friend void b_counts<T> ()
这里没有参数,因此必须使用函数模板语法<T>
来指明实例化。如:b_counts<int>()
是对应HasFriend<int>
模板类的友元,每种HasFriend<T>
模板类都有其自己的友元函数b_counts<T>()
[注]:
(1)这里需要显式指出函数模板具体化,<>
写法是模板函数实例化的一种语法,表示使用模板生成一个类型的函数定义;
(2)声明定义三步走(类前声明、类内声明、类外定义,类内声明要显式)
探讨友元函数加<>的意义(涉及函数模板的显示实例化、显示具体化)
如:
1. 使用 HasFriend<int> 创建对象 2. 隐式实例化了 HasFriend<int>类3. HasFriend<int> 类内的所有函数于是都被确定(使用int替换所有的T)4. 因为友元函数是模板函数,在类内以显式实例化声明 friend void b_report<> (HasFriend<T> &)模板类的模板类型确定后,该显式实例化声明的T也就确定变为:friend void b_report<HasFriend<int> >(HasFriend<int> &) (一个实例化的模板函数)7. 编译器看到上述声明后,将使用b_report()模板生成一个HasFriend<int> 类型的实例也就是使用b_report()模板生成一个 HasFriend<int> 类型的函数定义
其实在类内声明的b_report<>(HasFriend<T>&)
只是函数模板b_report(T& t)
的一个实例化,当给b_report 传参的实参类型为HasFriend<int>
时,它就实例化为了b_report<HasFriend<int> >(HasFriend<int> &)
,由函数模板实例化的用法可知,在这里就为其生成了定义
如果不加<>
对友元函数模板的显式实例化说明,b_report(HasFriend<int>&)
会找不到函数的定义,因为它不是一个模板函数,必须要去匹配b_report(HasFriend<int>&)
这个具体函数的定义。因为外面的b_report(T& t)
是一个函数模板,没有实例化b_report(HasFriend<int>&)
也就是没有它的具体定义。在汇编中可以看到,foo是没有被实例化的。
那为什么b_report(T& t)
不可以根据实参来实例化出一个b_report(HasFriend<int>&)
呢?因为根据匹配规则优先级,会去匹配更加具体的一个函数,也就是b_report(HasFriend<int>&)
,并没有匹配b_report(T& t)
,因为没有找到前者的定义,就报了链接错误。
对友元函数加了<>,就在告诉编译器,将b_report(T& t)
实例化为HasFriend<T>
类型的模板函数,但感觉还没完全具体化,随着模板类的实例化,类内的友元模板函数也随之确定实例。总之,就让链接器知道,友元函数b_report<>(HasFriend<T>&)
是有定义存在的。
非约束模板友元
友元函数是一个模板函数
template<typename T>class HasFriend{public:template<typename C, typename D>friend void show(C &, D & );...private:T data;};template<typename C, typename D>void show(C& c, D& d){cout << c.data << d.data;}
约束模板友元,是在类模板外面声明友元模板。通过在类模板内部声明友元模板,可以创建非约束模板友元,即每个函数模板的具体化都是每个类模板具体化的友元(所有类模板具体化的友元)。对于非约束模板友元,友元模板的类型参数和类模板的类型参数是不同的
如:
int main(){HasFriend<int> i(10);HasFriend<double> d(2.3);show(i, d);}
函数调用show(i, d)
与下面具体化匹配:
void show<HasFriend<int> , HasFriend<double> > (HasFriend<int>& c, HasFriend<double>& d)
这也说明了它是所有HasFriend模板类的友元
当然也可以这样写,看着更像是参数为HasFriend模板类的友元函数:
template<typename T>class HasFriend{...template<typename C, typename D>friend void show(HasFriend<C> &, HasFriend<D> & );}template<typename C, typename D>void show(HasFriend<C>& c, HasFriend<D>& d ){cout << c.data << d.data; // 这样有智能提示}
调用show(i, d)
时,类型推导的就变成了:
void show<int, double> (HasFriend<int>& c, HasFriend<double>& d)
主要影响的是show<C, D>
模板函数的模板参数类型的推导。但是还是不要写成这样,因为这样会使友元被约束住了(参数类型只能是HasFriend<T>
的某个具体化,而不是通用的),比如:
// 将show 改为:template<typename C, typename D>show( HasFriend<C>& c, HasFriend<D>& d ){cout << c.data;cout << d;}HasFriend<int> i(10);show(i, 10); // 错误,第二个参数错误
假如并不都需要直接访问两个模板类的成员,就没必要写成这样。将show写为 show(C& c, D& d) 的通用性更强,所以更能体现**”非约束“**一词
💡 约束 的原因:函数模板显式实例化语法<>
使一个函数模板实例化,实例化的模板函数的参数是一个模板类型,参数类型受到类模板的实例化类型影响。因此每生成一个具体的类,就会实例化一个与其对应的友元函数。
受模板类的类型影响→约束;不受模板类的类型影响→非约束
[总结]:如果类模板的友元函数是函数模板实例化,就是约束模板友元;如果类模板的友元函数是模板函数,就是非约束模板友元。
[建议]:关于模板友元——约束与非约束:
为了保持“约束”与”非约束“的风格统一性,如果一定会使用到模板类成员,就使用约束写法;如果不一定会使用到模板类成员,就使用非约束写法
// 约束模板友元写法// 类前声明:template<typename T>class HasFriend;template<typename T>void foo(T &);// 类内声明:friend void foo<> (HasFriend<T> &); // 对函数模板实例化// 类外定义:template<typename T>void foo(HasFriend<T>& t) // 函数模板的具体化,函数重载{// 直接访问模板类成员 t.data 还有智能提示,岂不美滋滋}
// 非约束模板友元写法// 类内声明:template<typename C, typename D>friend void foo(C &, D &);// 类外定义:template<typename C, typename D>void foo(C& c, D& d){// 用到c.data ,d是其他类型,不是 HasFriend的具体类}
模板类友元函数总结
根本原理,就是函数模板的具体化:约束条件,就是在类内对函数模板实例化声明;非约束条件,就是在类内声明一个与类模板类型不同的函数模板