深入探究模板类 IV:非模板友元&约束模板友元&非约束模板友元

文章深入分析了模板类中的三种友元类型:非模板友元、约束模板友元和非约束模板友元,讨论了它们的适用场景、优缺点以及实现方式。非模板友元简单但不通用,约束模板友元适用于特定模板实例,而非约束模板友元则可处理所有模板实例。结论指出,选择友元类型应基于功能是否依赖于模板参数。
摘要由CSDN通过智能技术生成

A-模板类友元的分类

  • 非模板友元
  • 约束(bound)模板友元
    即友元的类型取决于类被实例化时的类型
  • 非约束(unbound)模板友元
    即友元的所有具体化都是类的每一个具体化的友元

看《C++ Primer Plus》的时候,发现作者对这三种友元的介绍较为简略。经过一段时间的研究,我从个人的角度给出我对这三种友元的理解。这里我会先直接给出结论,再去给出得到结论的过程。
——文章的所有内容完全是基于个人的理解,逻辑和内容可能出现很大的错误,希望各位路过的大佬们及时指正

B-个人得出的结论

一般情况下用哪种友元

  • 如果功能不依赖于模板参数,请使用非模板友元
  • 如果功能依赖于特定类型的模板参数,请使用约束模板友元
  • 如果功能依赖于任意类型的模板参数,请使用非约束模板友元

三种友元的区别

  1. 非模板友元:本身就是普通的函数,没有任何的模板参数
    • 优点:简单,易于理解和使用
    • 缺点:不适合实现通用功能,因为它们不能处理不同类型的参数
  2. 约束模板友元:本身是类外声明的模板函数
    • 优点:可以实现通用功能,适用于特定类型的模板实例
    • 缺点:实现相对复杂,需要额外的模板参数
  3. 非约束模板友元:本身是类中声明的模板函数
    • 优点:可以实现更通用的功能,适用于任何类型的模板实例
    • 缺点:实现相对复杂,需要额外的模板参数

何时适用的文字描述

  • 其中1毫无悬念地,能够总结出这么一条规律:
    非模板友元函数主要用于实现不依赖于模板参数的功能
  • 约束模板友元:
    约束模板友元函数仅适用于特定的模板实例,因此它们可以根据特定的约束条件来实现通用功能
  • 非约束模板友元:
    非约束模板友元函数也是模板函数,但它们适用于所有模板实例,而不仅仅是特定类型的实例。这使得它们可以实现更通用的功能

现在将去论证这个结论是如何得出的:

C-三种友元的不同实现方式

前置:定义一个普通类Player

写一个普通类,用于实现最基础的功能:

class Player
{
private:
  static int count;
  int num;
public:
  Player() {};
  Player(int num_) :num(num_){};
publicfriend void counts();
  friend void show(Player& pt);
  ......
  • counts():访问类中静态类型数据
void counts()
{
  cout << "#<int>-static:" << Player::count << endl;
}
  • show():访问类中类型为T的num
void show(Player& pt)
{
  cout << "#<int>-num:" << pt.num << endl;
}

前置:让Player成为模板类-T

template <class T>
class Player
{
private:
  static int count;
  T num;
public:
  Player() {};
  Player(T num_) :num(num_){};
  ......
  • counts():注意counts()函数不是通过对象调用的,那么counts()要如何访问对象?
    ——有很多种可能性,它可以访问全局对象,可以使用全局指针访问非全局对象,可以创建自己的对象,可以访问模板类的静态数据成员。
    ——为了与之前的定义统一,在此情景下,设置counts()访问的是模板类的静态数据对象count
  • show():通过传参,把对象传入函数

在这之前,还要让静态成员count初始化:

template <class T>
int Player<T>::count = 101;
template <>
int Player<int>::count = 111;
template <>
int Player<double>::count = 222;

前置:typeid(num).name()获取变量数据类型

在C++中,可以通过包含头文件<typeinfo>,使用typeid(???).name()来获取变量数据类型:
这是一个使用的举例:

#include<typeinfo>
main:
	int num;
	cout << typeid(num).name() ;
-------------------------------------
输出:int

三种友元同时实现

#include<iostream>
#include<typeinfo>
using namespace std;
//这是约束模板友元函数的类前声明:
template<typename U> void counts_();
template<typename U> void show_(U& pt);

template <class T>
class Player
{
private:
  static int count;
  T num;
public:
  Player() {};
  Player(T num_) :num(num_){};
public:
//1.非模板友元函数
  friend void counts();
  friend void show(Player<T>& pt);
public:
//2.约束模板友元函数-U
  friend void counts_<T>();
  friend void show_<>(Player<T>& pt);
public:
//3.非约束模板友元函数-V
  template<typename V>
  friend void counts__();
  template<typename V>
  friend void show__(V & pt);
};
//静态成员初始化:
template <class T>
int Player<T>::count = 101;
template <>
int Player<int>::count = 111;
template <>
int Player<double>::count = 222;
/*---------------------------------------*/
//1.非模板友元函数
void counts()
{
  cout << "#<int>-static:" << Player<int>::count << endl;
  cout << "#<double>-static:" << Player<double>::count << endl;
}
void show(Player<int>& pt)
{
  cout << "#<int>-num:" << pt.num << endl;
}
void show(Player<double>& pt)
{
  cout << "#<int>-num:" << pt.num << endl;
}
/*---------------------------------------*/
//2.约束模板友元函数-U
template<typename U>
void counts_()
{
  U type;
  cout << "#<U>-static:" << typeid(type).name() 
    << "#:" << Player<U>::count << endl;
}
template<typename U>
void show_(U& pt)
{
  U type;
  cout << "#<U>-num:" << typeid(type).name() 
    << "#:" << pt.num << endl;
}
/*---------------------------------------*/
//3.非约束模板友元函数-V
template<typename V>
void counts__()
{
  V type;
  cout << "#<V>-static:" << typeid(type).name() 
    << "#:" << Player<V>::count << endl;
}
template<typename V>
void show__(V& pt)
{
  V type;
  cout << "#<V>-num:" << typeid(type).name() 
    << "#:" << pt.num << endl;
}
/*---------------------------------------*/
int main()
{
  Player<int> zmr(2333);
  //1.非模板友元函数:
  counts();
  show(zmr);
  cout << "-----------------\n";
  //2.约束模板友元函数-U:
  counts_<int>();
  show_(zmr);
  cout << "-----------------\n";
  //3.非约束模板友元函数-V:
  counts__<int>();
  show__(zmr);
  cout << "-----------------\n";

}

接下分别单独分析这三种友元:

1.非模板友元函数

本身就是普通的函数,没有任何的模板参数。

类外函数定义
  • counts():访问的是Player<int>Player<double>
void counts()
{
  cout << "#<int>-static:" << Player<int>::count << endl;
  cout << "#<double>-static:" << Player<double>::count << endl;
}
  • show():在外部的普通函数,是使用实例化的Player<int>Player<double>进行重载
void show(Player<int>& pt)
{
  cout << "#<int>-num:" << pt.num << endl;
}
------------------------------------------
void show(Player<double>& pt)
{
  cout << "#<int>-num:" << pt.num << endl;
}
友元声明
public:
  friend void counts();
  friend void show(Player<T>& pt);

注意:使用了show(Player<T>& pt)——这并不代表show()是模板函数,而是对应于每一个实例化的int,double,都让其有相对应的友元函数。
否则就要写成:

public:
  friend void counts();
  friend void show(Player<int>& pt);
  friend void show(Player<double>& pt);

这么做有一个问题:假如Player实例化为Player<string>,根据friend void show(Player<T>& pt),编译器理所应当地认为应当存在友元函数void show(Player<string>& pt),而实际上我们只为int,double进行了重载,并不存在string版本的show(),就会导致报错

主函数内运行:
main:
  Player<int> zmr(2333);
  counts();
  show(zmr);
-----------------
显示:
#<int>-static:111
#<double>-static:222
#<int>-num:2333

可以看到,将Player实例化为Player<int>是正常的,但是实例化为Player<string>,就会出现找不到函数void show(Player<string>& pt)的尴尬局面:

main:
  Player<string> zmr("qweasd");
  counts();
  show(zmr);
-----------------
报错:
 LNK2019  无法解析的外部符号 "void __cdecl show(class Player<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > &)" 
 (?show@@YAXAEAV?$Player@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@@@Z),
 函数 main 中引用了该符号 

2.约束模板友元函数-U

本身是类外声明的模板函数,其实就是一个最普通的模板函数
通过Step123三部实现约束模板友元函数:

Step1-类前函数声明

首先,在类定义的前面声明每个模板函数:

template<typename U> void counts_();
template<typename U> void show_(U& pt);

如果没有在类前声明,就会报错

Step2-类中友元声明

针对模板类型,分别对counts_()show_()实例化:

public:
  friend void counts_<T>();
  friend void show_<>(Player<T>& pt);
  • counts_():显式实例化

    由于counts_没有参数,因此必须使用模板参数语法来显式指明其实例化

  • show_<>():隐式实例化

    声明中的<>指出应该使用泛型版本,而空着则表明是隐式实例化,是因为可以从函数的参数推断出模板的类型,不过也可以写成显式实例化:show_<Player<T>>(Player<T>& pt)

Step3-类外函数定义

第三步是为模板函数提供定义。

template<typename U>
void counts_()
{
  U type;
  cout << "#<U>-static:" << typeid(type).name() 
    << "#:" << Player<U>::count << endl;
}

template<typename U>
void show_(U& pt)
{
  U type;
  cout << "#<U>-num:" << typeid(type).name() 
    << "#:" << pt.num << endl;
}
主函数内运行:
main:
  Player<int> zmr(2333);
  counts_<int>();
  show_(zmr);
-----------------
显示:
#<U>-static:int#:111
#<U>-num:class Player<int>#:2333

3.非约束模板友元函数-V

与普通的模板函数有一点不一样,本身是类中声明的模板函数。

类中函数声明+友元声明
public:
  template<typename V>
  friend void counts__();
  template<typename V>
  friend void show__(V & pt);
类外函数定义
template<typename V>
void counts__()
{
  V type;
  cout << "#<V>-static:" << typeid(type).name() 
    << "#:" << Player<V>::count << endl;
}

template<typename V>
void show__(V& pt)
{
  V type;
  cout << "#<V>-num:" << typeid(type).name() 
    << "#:" << pt.num << endl;
}
主函数内运行:
main:
  Player<int> zmr(2333);
  counts__<int>();
  show__(zmr);
-----------------
显示:
#<V>-static:int#:111
#<V>-num:class Player<int>#:2333

D-得出结论的过程:

1.使用非模板友元函数时,发现了这么个问题:

如果把友元声明当成一种双向承认的,决定是否能够访问类内部元素的"通行证",那么在前面我们使用非模板友元,做了一个小小的改进:

注意:使用了show(Player<T>& pt)——这并不代表show()是模板函数,而是对应于每一个实例化的int,double,都让其有相对应的友元函数。

但是随即带来了问题:

这么做有一个问题:假如Player实例化为Player<string>,根据friend void show(Player<T>& pt),编译器理所应当地认为应当存在友元函数void show(Player<string>& pt),而实际上我们只为int,double进行了重载,并不存在string版本的show(),就会导致报错

所以这么做,实际上是达到了这么一个效果:
修改友元声明,最终达成了效果:
——只针对当Player实例化为int或double时,zmr才有资格使用show()

2."约束"与"非约束"究竟是什么?

从之前非模板友元函数得到的总结,换一种说法,恰恰就是约束的概念:
约束:仅仅只允许Player实例化为特定类型时,zmr才有资格使用该友元函数
——那么为什么约束模板友元函数能做到约束,而非约束模板友元函数就不能?
还是回到前面约束模板友元函数例子中,比如现在我需要为counts_()show_()提供专门针对数组的版本,即为Player<T*>的版本,于是就可以在友元声明中添加:

public:
  friend void counts_<T>();
  friend void counts_<T*>();
  friend void show_<>(Player<T>& pt);
  friend void show_<>(Player<T*>& pt);

同时在类外提供counts_()show_()针对<T*>的显式具体化函数定义。
——但是非约束模板友元函数就做不到了:

public:
  template<typename V>
  friend void counts__();
  template<typename V>
  friend void show__(V & pt);

无论Player实例化成任何类型,要求counts__()show__()都必须能够处理Player,这无疑是给函数的定义提出了更高的要求
——这时候,就能给出非约束的概念了:
非约束:当Player实例化为任何类型时,都必须要要求该函数能够处理zmr
根本原因是非约束模板友元函数模板声明+友元声明合并了,而约束模板友元函数之所以能约束,是因为模板声明放在类前,和友元声明是分离的
看完1,2,这时候再回去看开头的B-个人得出的结论,也就一目了然了。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值