C++ 模板进阶(全)

C++ 模板进阶(全)

目录

一. 非类型模板参数

1.1 实例

二. 模板的特化

2.1 函数模板的特化

2.1.1 实例

2.1.2 函数模板的特化步骤

2.2 类模板的特化

2.2.1 全特化

2.2.2 偏特化/半特化

偏特化还可以特化指针和引用:

2.2.3 类模板特化应用实例

三. 模板分离编译

3.1 什么是分离编译

3.2 模板的分离编译

3.2.1 模板分离代码简单演示

3.2.2 分析运行过程

3.3 解决方法

四. 模板总结

4.1 模板的优点

4.2 模板的缺点


一. 非类型模板参数

模板参数分类型形参与非类型形参。

  • 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
  • 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

1.1 实例

template<size_t N> class Stack { private: int _a[N]; int top; };

1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。

2. 非类型的模板参数必须在编译期就确认结果。

二. 模板的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板。、

2.1 函数模板的特化

2.1.1 实例

//普通函数模板 template<class T> bool Less(T left, T right) { return left < right; } //特化模板 template<> bool Less<Date*>(Date* left, Date* right) { return left < right; } int main() { Date d1(2026, 10, 6); Date d2(2026, 7, 4); cout << Less(d1, d2) << endl; Date* p1 = &d1; Date* p2 = &d2; cout << Less(d1, d2) << endl; return 0; }

可以发现,当类型是指针时,我们原先的函数模板就无法发挥作用,因为比较的就是地址了而不是里面的值,那我们就可以对这个函数模板特化。

但是实践中,我们往往不写特化,直接写现成的函数

//普通函数模板 template<class T> bool Less(T left, T right) { return left < right; } //现成的函数 bool Less(Date* left, Date* right) { return left < right; } int main() { Date d1(2026, 10, 6); Date d2(2026, 7, 4); cout << Less(d1, d2) << endl; Date* p1 = &d1; Date* p2 = &d2; cout << Less(d1, d2) << endl; return 0; }

对编译器来说,有现成的函数就不会走模板的实例化,这相当于是特化了。但是为什么不推荐使用函数模板的特化呢,因为函数模板的特化有很大的坑。

//普通函数模板 template<class T> bool Less(const T& left,const T& right) { return left < right; } //特化模板 template<> bool Less<Date*>(Date* const& left, Date* const& right) { return left < right; } int main() { Date d1(2026, 10, 6); Date d2(2026, 7, 4); cout << Less(d1, d2) << endl; Date* p1 = &d1; Date* p2 = &d2; cout << Less(d1, d2) << endl; return 0; }

以防 T 是自定义类型,我们通常会加上 const 和 & 来避免拷给构造,但是 T 如果是指针,我们就要注意 const 修饰的是整个指针,也就是说 const 要放在 * 号后面来修饰指针本身。

2.1.2 函数模板的特化步骤

1、必须要先有一个基础的函数模板;

2、关键字template后面接一对空的尖括号<>;

3、函数名后跟一对尖括号,尖括号中指定需要特化的类型;

4、函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

2.2 类模板的特化

类模板特化是指针对某些特殊类型,为模板类提供一份专门的实现。当通用模板无法满足某种类型的特殊需求时,可以通过特化让该类型使用不同的代码逻辑。类模板特化分为全特化偏特化,全特化是将模板参数完全确定,例如template<> class A<int>;偏特化是只限定部分模板参数,例如template<class T> class A<T*>。编译器在实例化模板时,会优先匹配特化版本,如果没有匹配才使用普通模板。

2.2.1 全特化

全特化即将模板参数列表中所有的参数都确定化(所有模版都特化)

template<class T1,class T2> class Data { public: Data() { cout << "Date<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; template<> class Data<int, char> { public: Data() { cout << "Date<int, char>" << endl; } }; int main() { Data<int, int> a1; Data<int, char> a2; return 0; }

2.2.2 偏特化/半特化

偏特化也叫半特化,即任何针对模版参数进一步进行条件限制设计的特化版本部分模版都特化)

template<class T1,class T2> class Data { public: Data() { cout << "Date<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; template<class T1> class Data<T1, double> { public: Data() { cout << "Date<T1, double>" << endl; } }; int main() { Data<int, double> a3; return 0; }
偏特化还可以特化指针和引用:
template<class T1,class T2> class Data<T1*, T2*> { public: Data() { cout << "Date<T1*, T2*>" << endl; } }; template<class T1, class T2> class Data<T1&, T2&> { public: Data() { cout << "Date<T1&, T2&>" << endl; } }; int main() { Data<int*, double*> a4; Data<int&, double&> a5; return 0; }

2.2.3 类模板特化应用实例

在日期类比较的时候,我们发现指针比较是一个特例,所以我们对函数模板进行了特化;同样的在优先级队列中,我们需要仿函数完成比较,但是传递指针时正常的仿函数无法完成任务,我们也可以对类模板进行特化

template<class T> class Less { public: bool operator()(const T& x1,const T& x2)const { return x1 < x2; } }; template<class T> class Less<T*> { public: bool operator()(T* x1, T* x2)const { return *x1 < *x2; //要注意如果类型是 int* ,那么 T 的值应该是 int 而不是 int* !!!!! } };
int main() { lzx::priority_queue<Date*,vector<Date*>,lzx::Less<Date*>> q1; q1.push(new Date(2018, 5, 10)); q1.push(new Date(2018, 6, 10)); q1.push(new Date(2018, 7, 10)); q1.push(new Date(2018, 8, 10)); while (!q1.empty()) { cout << *q1.top() << ' '; q1.pop(); } return 0; }

这里特化有两种方向:

1. 只对Date*类型进行特化。

2. 针对指针类型进行特化。

三. 模板分离编译

3.1 什么是分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式

3.2 模板的分离编译

3.2.1 模板分离代码简单演示

Func.h:

#define _CRT_SECURE_NO_WARNINGS #pragma once template<class T> T Add(const T& left, const T& right);

Func.cpp:

#define _CRT_SECURE_NO_WARNINGS template<class T> T Add(const T& left, const T& right) { return left + right; }

Test.cpp:

#include"Func.h" int main() { Add(1.2); Add(1.0 2.0); return 0; }

3.2.2 分析运行过程

3.3 解决方法

#define _CRT_SECURE_NO_WARNINGS template<class T> T Add(const T& left, const T& right) { return left + right; } //显式实例化 template int Add(const int& left, const int& right); template double Add(const double& left, const double& rght);

1、将声明和定义放到一个文件“xxx.hpp"里面或者xxx.h其实也是可以的。推荐使用这种。
2、模板定义的位置显式实例化。这种方法不实用,不推荐使用。

四. 模板总结

4.1 模板的优点

1、模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生;
2、增强了代码的灵活性。

4.2 模板的缺点

1、模板会导致代码膨胀问题,也会导致编译时间变长;
2、出现模板编译错误时,错误信息非常凌乱,不易定位错误。