深入探究模板类 IV
非模板友元&约束模板友元&非约束模板友元
A-模板类友元的分类
- 非模板友元
- 约束(bound)模板友元
即友元的类型取决于类被实例化时的类型 - 非约束(unbound)模板友元
即友元的所有具体化都是类的每一个具体化的友元
看《C++ Primer Plus》的时候,发现作者对这三种友元的介绍较为简略。经过一段时间的研究,我从个人的角度给出我对这三种友元的理解。这里我会先直接给出结论,再去给出得到结论的过程。
——文章的所有内容完全是基于个人的理解,逻辑和内容可能出现很大的错误,希望各位路过的大佬们及时指正
B-个人得出的结论
一般情况下用哪种友元
- 如果功能不依赖于模板参数,请使用非模板友元
- 如果功能依赖于特定类型的模板参数,请使用约束模板友元
- 如果功能依赖于任意类型的模板参数,请使用非约束模板友元
三种友元的区别
- 非模板友元:本身就是普通的函数,没有任何的模板参数
- 优点:简单,易于理解和使用
- 缺点:不适合实现通用功能,因为它们不能处理不同类型的参数
- 约束模板友元:本身是类外声明的模板函数
- 优点:可以实现通用功能,适用于特定类型的模板实例
- 缺点:实现相对复杂,需要额外的模板参数
- 非约束模板友元:本身是类中声明的模板函数
- 优点:可以实现更通用的功能,适用于任何类型的模板实例
- 缺点:实现相对复杂,需要额外的模板参数
何时适用的文字描述
- 其中1毫无悬念地,能够总结出这么一条规律:
非模板友元函数主要用于实现不依赖于模板参数的功能 - 约束模板友元:
约束模板友元函数仅适用于特定的模板实例,因此它们可以根据特定的约束条件来实现通用功能 - 非约束模板友元:
非约束模板友元函数也是模板函数,但它们适用于所有模板实例,而不仅仅是特定类型的实例。这使得它们可以实现更通用的功能
现在将去论证这个结论是如何得出的:
C-三种友元的不同实现方式
前置:定义一个普通类Player
写一个普通类,用于实现最基础的功能:
class Player
{
private:
static int count;
int num;
public:
Player() {};
Player(int num_) :num(num_){};
public:
friend 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-个人得出的结论
,也就一目了然了。