纵有疾风起,人生不言弃。本文篇幅较长,如有错误请不吝赐教,感谢支持。
类模板与友元函数
模板类的友元函数有三类:
- 1)非模板友元函数:友元函数不是模板函数,而是利用模板类参数生成的函数。
- 2)约束模板友元函数:模板类实例化时,每个实例化的类对应一个友元函数。
- 3)非约束模板友元函数:模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数。
1️⃣非模板友元函数
非模板友元函数就是将一个普通函数声明为友元函数。我们先来看一段代码:
#include <iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Maker
{
private:
T1 m_x;
T2 m_y;
public:
Maker(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
Maker<string,int> m1("感谢支持强风吹拂king的博客",666);
void show()
{
cout << "x = " << m1.m_x << ", y = " << m1.m_y << endl;
}
int main()
{
show();
}
📝解释:
Maker是模板类,有两个模板参数T1,T2,在模板Maker类中用T1和T2分别定义了m_x,和m_y,构造函数使用初始化列表给m_x,和m_y赋值。
用模板类Maker定义全局对象m1,再定义全局函数show(),在全局函数show()中访问模板类Maker的私有成员,看下面运行结果(报错):
如果我们把全局函数show(),给声明成模板Maker的友元函数可以访问Maker的私有成员吗?
friend void show();
🖲执行结果如下:
在类模板Maker中,将普通函数show()函数声明为友元函数,则show()函数是类模板Maker所有实例的友元函数。例如,它是Maker<string,int>,Maker<string,int>,Maker<string,double>等的友元函数。
这种为类模板设置友元函数的方法在语法上没有任何错误,但是要用模板类去创建全局对象,代码这么写实在是太笨了,在学习友元的时候,如果Maker是普通类,我们可以把Maker普通类的引用传给友元函数而不必创建全局对象。
那么Maker是类模板,可以这么写吗?答案是不可以。Maker只是类的通用描述,根本不存在类名叫Maker的类,但是你具体化函数模板,也就是后面加上参数列表<具体的数据类型>,这就可以当做一个具体的类,就可以像普通类一样使用。
#include <iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Maker
{
private:
T1 m_x;
T2 m_y;
public:
Maker(const T1 x, const T2 y) : m_x(x), m_y(y) {}
friend void show(const Maker<string, int>& a);
};
void show(const Maker<string, int>& a)
{
cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
Maker<string,int> m1("感谢支持强风吹拂king的博客",777);
show(m1);
return 0;
}
🖲执行结果如下:
如果main函数内部我们这样写,编译器会报错吗?
int main()
{
Maker<char,int> m1('k',666);
show(m1);
return 0;
}
❌错误如下:
那么我们重载show函数,为Maker<char,int> m1(“k”,666);单独写一个show函数,并声明为Maker的友元函数。
void show(const Maker<char, int>& a)
{
cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
🖲运行结果如下:
编译通过了,但是问题来了,现在这种方法解决友元函数的问题,但是很麻烦,要为模板类的每一个实例化版本都要写一个友元函数,所以,C++提供了一个解决方案,利用模板参数,自动生成友元函数,这个友元函数也叫作非模板友元。
非模板友元:友元函数不是模板函数,而是利用模板类参数生成的函数,只能在类内实现。
#include <iostream>
#include<string>
using namespace std;
template<class T1, class T2>
class Maker
{
private:
T1 m_x;
T2 m_y;
public:
Maker(const T1 x, const T2 y) : m_x(x), m_y(y) {}
//友元函数的形参用模板的通用类型
friend void show(const Maker<T1, T2>& a)
{
cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
};
int main()
{
Maker<string, int> m1("感谢支持强风吹拂king的博客", 666);
show(m1);
Maker<char, int> m2('k', 666);
show(m2);
return 0;
}
🖲运行结果如下:
这种方法的本质是编译器利用模板参数为我们生成友元函数,但有一点我们要注意⚠️:
编译器利用模板参数生成友元函数,但是这个友元函数不是函数模板。
📝我举个例子证明“友元函数不是函数模板”一下:
#include <iostream>
#include<string>
using namespace std;
template<class T1, class T2>
class Maker
{
private:
T1 m_x;
T2 m_y;
public:
Maker(const T1 x, const T2 y) : m_x(x), m_y(y) {}
//友元函数的形参用模板的通用类型
friend void show(const Maker<T1, T2>& a)
{
cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
friend void show(const Maker<string, int>& a);
friend void show(const Maker<char, int>& a);
};
void show(const Maker<char, int>& a)
{
cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
void show(const Maker<string, int>& a)
{
cout << "x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
Maker<string, int> m1("感谢支持强风吹拂king的博客", 666);
show(m1);
Maker<char, int> m2('k', 666);
show(m2);
return 0;
}
这两个有具体类型的函数和模板参数的函数,它们之间是什么关系?
如果上面带模板参数的是函数模板,那两个带具体类型的就是函数模板的具体化版本。在模板技术中,普通函数,具体化版本和函数模板是可以共存的,编译器会优先使用普通函数和具体化版本,现在这三个函数之间的关系也是这样的吗?
函数出现了重载,为什么编译器不是优先选择一个函数调用,而是报重定义的错误呢?原因很简单,void show(const Maker<T1, T2>& a)不是函数模板,只是借用模板参数,编译器创建模板类实例的时候,会用具体的数据类型去替代模板参数,生成友元函数的实体,与下面定义的具体类型的函数发生代码冲突。报重定义的错误。
💬小结一下:
从上面的程序中我们可以看出,非模板友元的使用特别得呆板。
- 如果我们想为某种具体化的类模板单独写友元函数,这是做不到的,会报重定义的错误。(例如给Maker<string,int>单独写个友元函数,是不行🙅🏻♂️的)
- 非模板友元函数,在哪个模板类定义,就只能用于这个类,不能用于其他类,因为非模板友元借助的是本类模板参数,并且还只能在本类的类内定义。
2️⃣约束模板友元函数
约束模板友元函数是将一个函数模板声明为类的友元函数。函数模板的实例化类型取决于类模板被实例化时的类型,类模板实例化时会产生与之匹配的具体化友元函数。
📝使用约束模板友元的步骤:
- 第一步,在模板类的定义前,声明友元函数模板,为的是让模板类知道友元函数模板的存在。
- 第二步再次声明友元函数模板,在类模板中将函数模板声明为友元函数。在声明友元函数时,函数模板要实现具体化,即函数模板的模板参数要与类模板的模板参数保持一致,目的是让编译器知道需要实例化的友元函数模板,类模板和函数模板本来没什么关系的,正是第二步让它们产生了关系,编译器在实例化某种数据类型的类模板时,也会实例化这种数据类型的模板函数。
- 第三步,友元函数模板的定义,放在类模板下,因为友元函数是函数模板,所以可以和具体化的函数模板共存,不会发生代码冲突,报重定义的错误。
#include <iostream>
#include<string>
using namespace std;
// 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
template <typename T>
void show(T& a); // 第一步:在模板类的定义前面,声明友元函数模板。
template<class T1, class T2>
class AA // 模板类AA。
{
friend void show<>(AA<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
T1 m_x;
T2 m_y;
public:
AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> // 第三步:友元函数模板的定义。
void show(T& a)
{
cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(AA<int, string>& a)
{
cout << "具体AA<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
AA<int, string> a1(66, "感谢支持强风吹拂king的博客");
show(a1); // 将使用具体化的版本。
AA<char, string> a2(66, "感谢支持强风吹拂king的博客");
show(a2);// 将使用通用的版本。
}
🖲运行结果如下:
解释:
第一次在声明时,show()函数与AA类模板没有关系,只是让编译器知道函数模板的存在,第二步,函数模板再次声明,并完成显示实例化,模板参数变为了AA类,当生成AA< char,string>模板类时,会生成与之匹配的show< char,string>()函数和作为友元函数。
这种友元函数模板可以应用于多个模板类,只是第二步,函数模板在类内具体化不一样。
把类模板AA的代码复制一遍,将类名改为BB。
📝代码举例:
#include <iostream> // 包含头文件
#include<string>
using namespace std;
// 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数。
template <typename T>
void show(T& a); // 第一步:在模板类的定义前面,声明友元函数模板。
template<class T1, class T2>
class AA // 模板类AA。
{
friend void show<>(AA<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
T1 m_x;
T2 m_y;
public:
AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template<class T1, class T2>
class BB // 模板类BB。
{
friend void show<>(BB<T1, T2>& a); // 第二步:在模板类中,再次声明友元函数模板。
T1 m_x;
T2 m_y;
public:
BB(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> // 第三步:友元函数模板的定义。
void show(T& a)
{
cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(AA<int, string>& a)
{
cout << "具体AA<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <> // 第三步:具体化版本。
void show(BB<int, string>& a)
{
cout << "具体BB<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
AA<int, string> a1(66, "感谢支持强风吹拂king的博客");
show(a1); // 将使用具体化的版本。
AA<char, string> a2(66, "感谢支持强风吹拂king的博客");
show(a2); // 将使用通用的版本。
BB<int, string> b1(66, "感谢支持强风吹拂king的博客");
show(b1); // 将使用具体化的版本。
BB<char, string> b2(66, "感谢支持强风吹拂king的博客");
show(b2); // 将使用通用的版本。
}
🖲运行结果如下:
关于友元函数的声明,多说一点:
template<typename U>//类模板的定义
class A
{
……//其他成员
friend void func<U>();//声明无参友元函数func()
friend void show<>(A<U>& a);//声明有参友元函数show()
……//其他成员
};
在上述代码中,将函数模板func()与show()声明为类的友元函数
func()是无参的函数模板,我们在学习函数模板的时候,必须显示或隐式的让编译器知道模板参数将要被哪个具体的数据类型替换,以便生成具体的函数定义,但这是无参的函数模板,隐式实例化不行(没参数),所以只能借助参数列表显示实例化。
show()函数模板有一个模板类参数,编译器可以根据函数参数隐式推导出模板参数,生成函数定义,因此show()函数模板具体化中<>可以为空。
💬小结一下:
两个要点:
- 可以某种具体化的类模板单独写友元函数,例如上面代码中为AA<int, string> a1(66, “感谢支持强风吹拂king的博客”);提供单独具体化的友元函数
- 可以支持多个类模板。
这种友元方案更有价值,就是语法稍微麻烦一点。
3️⃣非约束模板友元函数
模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数。
这其实不科学,按理说每个实例化的类只需要一个友元函数即可,不需要n个,其他数据类型的友元函数和自己没关系,其实原因就是没有约束模板友元函数的第二步,没有将函数模板具体化,函数模板和类模板没有绑定起来。
声明非约束模板友元函数示例代码如下所示:
📝案例:文章来源:https://www.toymoban.com/news/detail-502163.html
#include <iostream> // 包含头文件。
using namespace std;
// 非类模板约束的友元函数,实例化后,每个函数都是每个每个类的友元。
template<class T1, class T2>
class AA
{
template <typename T> friend void show(T& a); // 把函数模板设置为友元。
T1 m_x;
T2 m_y;
public:
AA(const T1 x, const T2 y) : m_x(x), m_y(y) { }
};
template <typename T> void show(T& a) // 通用的函数模板。
{
cout << "通用:x = " << a.m_x << ", y = " << a.m_y << endl;
}
template <>void show(AA<int, string>& a) // 函数模板的具体版本。
{
cout << "具体<int, string>:x = " << a.m_x << ", y = " << a.m_y << endl;
}
int main()
{
AA<int, string> a1(66, "感谢支持强风吹拂king的博客");
show(a1); // 将使用具体化的版本。
AA<char, string> a2(66, "感谢支持强风吹拂king的博客");
show(a2);// 将使用通用的版本。
}
🖲运行结果如下:
虽然说运行结果和约束模板友元函数一致,但还是推荐使用约束模板友元函数。文章来源地址https://www.toymoban.com/news/detail-502163.html
到了这里,关于C++——详解类模板与友元函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!