模板(下)

156 阅读4分钟

非类型模板参数

模板参数分为 类型模板参数 和 非类型模板参数.

非类型模板参数,是用一个整型常量作为 类模板/函数模板的一个参数.

例:在stl的deque容器中,可以在模板参数里显式传一个buffer容纳的数据量.

image.png

模板特化

--引入

在某些场景下,要对某些具体类型进行特殊化处理.

为特定类型定义专用化模板,就是模板特化.

例:当对vector里的数据进行排序时,如果vector里存的是数据的地址,需要特殊处理.

template<class T>
void mySort(vector<T>& v)
{
	//用算法库的sort,传less默认排升序
	sort(v.begin(), v.end(), less<T>());
}

void test()
{
	//存待排序数字
	int number[] = { 8, 7, 6, 5};

	//存待排序数字的地址
	int* numberAdr[] = { number, number + 1, number + 2, number + 3 };

	//此时一个vector存待排序数字,另一个vector存待排序数字地址
	
	vector<int> vNum(number, number + 4);
	mySort(vNum);//此时T实例化为int,对数据进行排序
	for (auto x : vNum)
		cout << x << " ";
	cout << endl;

	vector<int*> vNumAdr(numberAdr, numberAdr + sizeof(numberAdr) / sizeof(numberAdr[0]));
	mySort(vNumAdr);//此时T实例化为int*,对数据地址进行排序,不符合要求
	for (auto x : vNumAdr)
		cout << *x << " ";
	cout << endl;
}

--类模板特化

前提:类模板的特化不能单独存在,必须先要有完整的类模板.

格式如下:

//完整的类模板
template<class T1, class T2>
struct Test
{
	Test()
	{
		cout << "泛化版本Test" << endl;
	}
};


template<>//填写没有被特化的模板参数
struct Test<int, int>//类后面<>有序填写 所有模板参数 和 被特化/被特殊处理的类型
{
	Test()
	{
		cout << "T1、T2都被特化版本,T1和T2都为int类型,Test类会特殊处理1" << endl;
	}
};

template<class T2>//填写没有被特化的模板参数
struct Test<int, T2>//类后面<>有序填写 所有模板参数 和 特化类型
{
	Test()
	{
		cout << "T1被特化版本, T1为int类型,Test类会特殊处理2" << endl;
	}
};

template<class T1>//填写没有被特化的模板参数
struct Test<T1, int>//类后面<>有序填写 所有模板参数 和 特化类型
{
	Test()
	{
		cout << "T2被特化版本,T2为int类型,Test类走这步特殊处理3" << endl;
	}
};
void test1()
{
  //根据传递的类型,走不同的分支/特殊处理不同情况
	Test<int, int> t1;
	Test<int, double> t2;
	Test<char, int> t3;
	Test<char, char> t4;
}

--特化的分类

( 1 ) 全特化:指定模板参数列表里的所有模板参数.

例:

template<>//填写没有被特化的模板参数
struct Test<int, int>//类后面<>有序填写 所有模板参数 和 被特化/被特殊处理的类型
{
	Test()
	{
		cout << "T1、T2都被特化版本,T1和T2都为int类型,Test类会特殊处理1" << endl;
	}
};

( 2 ) 偏特化:将一部分模板参数进行特化.

例:

template<class T2>//填写没有被特化的模板参数
struct Test<int, T2>//类后面<>有序填写 所有模板参数 和 特化类型
{//类内特殊处理方式};

template<class T1>//填写没有被特化的模板参数
struct Test<T1, int>//类后面<>有序填写 所有模板参数 和 特化类型
{};

--函数模板特化

前提:函数模板的特化不能单独存在,必须要有完整的函数模板/泛化版本

注意:函数模板的特化只能进行全特化

格式与类模板特化类似

template<class T1, class T2>
void func(T1 a1, T2 a2)
{
	cout << "<T1、T2>泛化版本" << endl;
}

template<>
void func<int, int>(int a1, int a2)
{
	cout << "<int, int>特化版本" << endl;
}

template<>
void func<double, int>(double a1, int a2)
{
	cout << "<double, int>特化版本" << endl;
}
void test2()
{	
	func<int, int>(1, 2);
	func<double, int>(1.1, 2);

	//也能让编译器自己推导类型
	func(1, 2);
	func(1.1, 2);
	func('a', 'b');
}

--特殊的偏特化

//相当于指定类型的范围,只要都是指针/引用,走特化版本
template<class T1, class T2>
struct Test<T1*, T2*>
{
	Test()
	{
		cout << "<指针, 指针>" << endl;
	}
};

template<class T1, class T2>
struct Test<T1&, T2&>
{
	Test()
	{
		cout << "<引用, 引用>" << endl;
	}
};
void test3()
{
	Test<int*, char*> t1;
	Test<int&, char&> t2;
	Test<int*, char&> t3;
}

模板参数是引用或指针的例子:

在实现迭代器时,Iterator会有3个模板参数

<class T, class Ref, class Ptr>

T:数据类型

Ref:用来接收 T& 或 const T&,作为operator*()返回值区分普通迭代器和const迭代器

Ptr:与Ref同理

模板的分离编译

--普通类与模板类分离编译比较

非模板类

在头文件中定义的普通类,内部的函数:

小函数放在类内作为内联函数;

大函数声明放在类内,定义放在另外.cpp文件(声明类域即可)

模板类

不支持函数声明和定义放在不同的文件中.

会出现链接错误,找不到分离编译的函数地址

--错误演示

为什么 模板类内部函数的声明和定义 不能放在不同的两个文件呢?

例:

vector.hpp

	template<class T>
	class vector
	{
		//声明和定义在不同文件的函数
		void reserve(size_t n);
		void push_back(const T& x);
	};

vector.cpp:有它们的具体实现

#include "vector.hpp"
template<class T>
void lyh::vector<T>::reserve(size_t n)
{
	if (n > capacity())
	{
		//开空间,拷贝数据
		T* newBlock = new T[n];
		for (size_t i = 0; i < size(); ++i)
		{
			newBlock[i] = _start[i];
		}
		//memcpy(newBlock, _start, sizeof(T) * size());

		//记录当前size()的值,因为它内部是_finish - _start算出来的,更新成员变量时,不能使用size()
		size_t st = size();

		//释放旧空间
		delete[] _start;

		//更新成员变量
		_start = newBlock;
		_finish = newBlock + st;
		_end_of_storage = newBlock + n;
	}
}

template<class T>
void lyh::vector<T>::push_back(const T& x)
{
	//1 检查扩容
	if (_finish == _end_of_storage)
		reserve(capacity() == 0 ? 5 : 2 * capacity());
	//2 插入数据
	*_finish = x;
	++_finish;
}

test.cpp中

void test()
{
	lyh::vector<int> v;
	v.push_back(1);
}

image.png

--具体原因

在预处理阶段,.hpp头文件的内容将会在两个.cpp文件中展开.

( 1 ) 在链接之前,各个.cpp文件时分别进行编译汇编,不会有任何交互

( 2 ) 在test.cpp中,有指定vector,进行显式实例化,

这些没有分离的成员函数也会实例化出一份具体的函数,有函数地址

声明和定义在不同文件的函数,只有声明,那只能在链接阶段去其它文件找函数地址

( 3 ) vector.cpp中,里面全都是模板,无法进行实例化,

所以符号表里没有这些函数的地址.

综上:找不到对应的函数,会发生链接错误

--解决方案

( 1 ) 模板类内部的 函数声明 和 定义 要放在同一文件中.

分离也必须在同一文件中分离.

( 2 ) 在.cpp中手动显式实例化

例:在vector.cpp的结尾加上:

template
vector<int>;    

让编译器在该文件中手动实例化出一份vector类型

但可能还需要实例化出存储double、char、以及各种自定义类型的vector,不建议使用这种方案.