C++ 泛型编程(二):非类型模板参数,模板特化,模板的分离编译

非类型模板参数

模板的参数分为两种,一种是非类型参数,一种是类型参数。
类型参数:则是我们通常使用的方式,就是在模板的参数列表中在class后面加上参数的类型名称。
非类型参数:非类型参数则是用一个常量作为模板的参数,在模板中可以当作常量来使用,通常是需要指明大小或者初始化内容的才会用这种。

比较常见的就是c++中的array
在这里插入图片描述
array的底层就是直接使用的数组,而数组创建时必须指明大小,并且大小得是个常量,所以就会用到非类型模板参数。

注意
1. 浮点数、、自定义类型、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。

通常情况下非类型模板参数都是使用字符型和整型


函数模板的特化

当我们使用模板来实现一个函数,肯定是想利用它来解决逻辑相同但数据类型不同的一些问题,来实现代码的复用,但是也存在某些特例,比如针对某一情景或者某一类型,这个模板需要有特殊的处理,这个时候就需要用到模板的特化。

例如:

template<class T>
bool IsEqual(T str1, T str2)
{
	return str1 == str2;
}

int main()
{
	char str1[] = "hello";
	char str2[] = "hello";

	if (IsEqual(str1, str2))
		cout << "true";
	else
		cout << "false";
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200605172044348.png
这里不同的原因是传递过去的是两个char*类型,他们两个比较的不是字符串的内容,而是指针的地址,这里str1是在栈上开辟一块空间后再将hello拷贝过去,而直接传参的hello则是代码段中的常量,两者不可能相同。

如果要比较char*,就得用到strcmp来对这个情况进行特殊处理, 也就是模板的特化。

bool IsEqual<char*>(char* str1, char* str2)
{
	return strcmp(str1, str2) == 0;
}

在这里插入图片描述

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同

类模板的特化

类也是同理,如果需要有特殊情景也需要特化处理
下面拿这个类举例子

template<class T1, class T2>
class test
{
public:
	test()
	{
		cout << "test<T1, T2>" << endl;
	}

private:
	T1 _x;
	T2 _y;
};

全特化

全特化即是将模板参数列表中所有的参数都确定化。
例如:
这里对test<int,double>版本特化

template<>
class test<int, double>
{
public:
	test()
	{
		cout << "test<int, double>" << endl;
	}

private:
	int _x;
	double _y;
};

int main()
{
	test<double, double> t1;
	test<int, double> t2;
}

int main()
{
	test<double, double> t1;
	test<int, double> t2;
}

在这里插入图片描述


偏特化

偏特化即是任何针对模版参数进一步进行条件限制设计的特化版本

偏特化有两种表现方式,一种是部分参数特化,一种是参数修饰特化

部分参数特化

这里对第二个参数特化,只要第二个参数是double就会调用对应特化版本

template<class T1>
class test<T1, double>
{
public:
	test()
	{
		cout << "test<T1, double>" << endl;
	}

private:
	T1 _x;
	double _y;
};

int main()
{
	test<double, double> t1;
	test<int, double> t2;
	test<float, double> t3;
}

在这里插入图片描述

参数修饰特化

比如用指针或者引用来修饰类型,也可以进行特化

template<class T1, class T2>
class test<T1*, T2*>
{
public:
	test()
	{
		cout << "test<T1*, T2*>" << endl;
	}

private:
	T1* _x;
	T2* _y;
};

int main()
{
	test<double, int> t1;
	test<int*, double*> t2;
	test<float*, double*> t3;
}

在这里插入图片描述


模板分离编译

对于一个代码量比较多的项目,通常都会采用声明与定义分离的方法,比如在头文件进行声明,在源文件完成代码的实现,最后通过链接的方法链接成单一的可执行文件。但是C++的编译器却不支持模板的分离编译,一旦进行分离编译,就会出现链接错误。

问题分析

//头文件a.h
template<class T>
bool IsEqual(const T& str1, const T& str2);

-------------
//源文件a.cpp
template<class T>
bool IsEqual(const T& str1, const T& str2)
{
	return str1 == str2;
}
--------------
//test.c
#include<iostream>
#include"a.h"
using namespace std;

int main()
{
	cout << IsEqual(3, 5);

	cout << IsEqual('a', 'b');
}

这里看上去是没有问题的,但是涉及到了模板的实例化规则。

当主函数调用这个函数的时候他就会去头文件中找到函数的声明,再通过声明找到a.h中的实现。
但是对于模板却并不会这样,因为上一章说过,模板的实例化只会在其第一次使用的时候才会进行,例如这里IsEqual(3, 5),他就会去头文件中寻找,但是头文件中只有声明,没有定义,无法将其实例化。他又想通过找到a.cpp中的函数定义来进行实例化,但是遗憾的是,a.cpp中只有IsEqual(const T& str1, const T& str2)的定义,没有IsEqual(const int & str1, const int T& str2),因为在a.cpp中并没有使用到该类型的实例,所以自然也不会为其实例化出来,这时test.cpp中就根本无法找到这个函数的实现,就导致了链接失败。
在这里插入图片描述

解决方法

这个问题其实没有什么完美的解决方法

  1. 将声明和定义放到同一个头文件中。(导致头文件代码过于庞大)
  2. 模板定义的位置显式实例化。(不实用)

这个问题刘未鹏大佬写的非常好,可以学习一下他的博客
为什么C++编译器不能支持对模板的分离式编译

猜你喜欢

转载自blog.csdn.net/qq_35423154/article/details/106574330