C++模板-进阶篇
目录
一、非类型模板参数
二、模板的特化
2.1 函数模板
2.2 类模板
2.2.1 全特化
2.2.2 偏特化
三、模板的分离编译
3.1 C/C++编译过程
3.2 为什么不能分离编译
一、非类型模板参数
非类型参数是相对于类型参数而言的
- 类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
- 非类型形参:就是用一个常量作为类(函数)模板的一个参数
template<size_t N = 10>
class Stack
{
private:int _a[N];int _top;
};
int main()
{stack s1<5>;stack s2<6>;
}
二、模板的特化
模板特化(Template Specialization)是指针对特定的模板参数提供特定版本的模板实现。通过模板特化,可以为特定类型或值提供定制化的模板实现,以满足特定需求或提高性能。
模板特化分为两种:函数模板特化和类模板特化。
2.1 函数模板
语法规定
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>,里面什么都不用写
- 函数名后跟一对尖括号<>,尖括号中指定需要特化的类型
- 函数形参表必须要和模板函数的基础参数类型完全相同
template<class T>
bool Less(const T& left, const T& right)
{return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date*& const left, Date*& const right)
{return *left < *right;
}
2.2 类模板
2.2.1 全特化
template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};
template<>
class Data<int, char>
{
public:Data() {cout << "Data<int, char>" << endl;}
private:int _d1;char _d2;
};
2.2.2 偏特化
类模板的偏特化主要有两种形式:个数偏特化和范围偏特化。
个数偏特化:类似缺省值里面的半缺省
template<class T1, class T2>
class Data
{
public:Data() {cout<<"Data<T1, T2>" <<endl;}
private:T1 _d1;T2 _d2;
};
template<>
class Data<T1, char>
{
public:Data() {cout<<"Data<int, char>" <<endl;}
private:int _d1;char _d2;
};
范围偏特化:针对特定范围内的类型参数进行偏特化。这种偏特化通常用于处理特定类型的变量,如const变量、指针或引用类型等。
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:T1 _d1;T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:Data(const T1& d1, const T2& d2): _d1(d1), _d2(d2){cout<<"Data<T1&, T2&>" <<endl;}
private:const T1 & _d1;const T2 & _d2;
};
三、模板的分离编译
3.1 C/C++编译过程
- 预处理:预处理器 (
cpp
) 处理以#
开头的指令,如#include
、#define
和#ifdef
。它通过展开宏和包含头文件来准备源代码进行编译。 - 编译:编译器 (
gcc
,clang
) 将预处理后的源代码翻译成特定于目标体系结构的汇编语言。 - 汇编:汇编器 (
as
) 将汇编代码转换为可重定位的机器码(目标代码)。 - 链接:链接器 (
ld
) 将多个目标文件组合在一起,并解决外部引用,生成最终的可执行代码。其主要是执行符号解析、为变量和函数分配最终地址,并链接必要的库文件。所有用 C 编写的程序都使用库函数。这些库函数是预先编译的,并且这些库文件的目标代码以.lib
(或.a
)扩展名存储。链接器的主要工作是将库文件的目标代码与我们程序的目标代码相结合。
3.2 为什么不能分离编译
-
模板实例化发生在编译时,编译器需要知道模板的完整定义才能生成正确的代码。如果将模板的定义和实现分开,编译器在实例化模板时无法找到完整的模板定义,因此无法正确生成代码。
-
模板通常包含多个类型参数,这些参数的类型可能相互依赖。例如,一个模板类的成员函数可能需要访问另一个成员变量,而这些成员变量的类型取决于模板参数。在这种情况下,编译器需要知道所有相关的模板参数才能正确地解析和生成代码。
-
模板代码通常包含一些复杂的元编程技巧,如模板特化、模板偏特化等。这些技巧需要在编译时进行计算和处理,以便正确地生成代码。如果将模板的定义和实现分开,编译器可能无法正确处理这些元编程技巧。
-
模板代码通常与特定的类型紧密相关,这意味着它们通常不会在不同的编译单元中被使用。因此,将模板的定义和实现分开可能会导致额外的编译开销,而没有带来明显的性能提升。