【C++】模板类的友元函数

模板类友元函数

模板类的友元函数

参考:https://blog.youkuaiyun.com/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的具体类
}

模板类友元函数总结

根本原理,就是函数模板的具体化:约束条件,就是在类内对函数模板实例化声明;非约束条件,就是在类内声明一个与类模板类型不同的函数模板

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值