【重走C++学习之路】12、模板进阶
目录
一、非类型模板参数
二、模板的特化
2.1 函数模板特化
2.2 类模板的特化
1. 全特化
2. 偏特化
3. 特化的匹配规则
三、模板的分离编译
结语
一、非类型模板参数
模板参数的类型有两类:
类型模板参数:用
typename
或class
声明,传递类型(如template<typename T>
)。非类型模板参数:传递具体的值,需在编译期确定(如
template<int N>
)。
非类型模板参数是一种特殊的模板参数,允许在模板中传递编译期常量值,而不是传递类型。通过非类型模板参数,可以在模板实例化时动态指定某些固定值,从而生成不同行为的模板代码。
示例:
template <class T, size_t N = 20>
class Array{public:Array():_size(N){}T& operator[](size_t i){return _arr[i];}const T& operator[](size_t i) const{return _arr[i];}size_t size(){return _size;}bool empty(){return _size == 0;}private:T _arr[N];size_t _size;};void TestArray(){Array<int, 10> arr;for (size_t i = 0; i < arr.size(); ++i){arr[i] = i;}for (size_t i = 0; i < arr.size(); ++i){cout << arr[i] << " ";}cout << endl;}
}
支持的参数类型:
整型(
int
、char
、size_t
等)枚举(
enum
)指针或引用(指向全局对象、静态成员或函数)
C++20 起支持浮点数(如
template<double Value>
)
二、模板的特化
模板特化是一种针对特定类型或模板参数提供定制化实现的机制,允许开发者覆盖通用模板的默认行为,从而优化性能、处理特殊类型或解决通用模板无法覆盖的场景。模板特化分为全特化和偏特化两种形式。
2.1 函数模板特化
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
示例:
// 通用函数模板
template <typename T>
void print(T value)
{cout << "Generic: " << value << endl;
}// 全特化:针对 T = const char*
template <>
void print<const char*>(const char* value)
{cout << "Specialized: " << value << endl;
}// 使用示例
print(10); // 输出 "Generic: 10"
print("Hello"); // 输出 "Specialized: Hello"
函数模板不支持偏特化,但可以全特化。不过更推荐使用函数重载替代特化,因为重载更灵活且优先级规则更清晰。
// 通用模板
template <typename T>
void print(T value)
{cout << "Generic: " << value << endl;
}// 重载版本处理 const char*
void print(const char* value)
{cout << "Overloaded: " << value << endl;
}
2.2 类模板的特化
1. 全特化
将模板参数列表中所有的参数都确定化。
// 通用模板
template <typename T>
class MyClass
{
public:void print() { cout << "Generic Template\n"; }
};// 全特化:针对 T = int 的完全特化版本
template <>
class MyClass<int>
{
public:void print() { cout << "Specialized for int\n"; }
};// 使用示例
MyClass<double> obj1; // 使用通用模板
obj1.print(); // 输出 "Generic Template"MyClass<int> obj2; // 使用全特化版本
obj2.print(); // 输出 "Specialized for int"
2. 偏特化
仅对模板的部分参数进行特化,或对参数的某些特性(如指针、引用、常量性)进行特化。偏特化仅适用于类模板,不适用于函数模板。
// 通用模板
template <typename T, typename U>
class MyPair
{
public:void print() { cout << "Generic Pair\n"; }
};// 偏特化:当两个类型相同时的特化版本
template <typename T>
class MyPair<T, T>
{
public:void print() { cout << "Same Type Pair\n"; }
};// 偏特化:针对指针类型的特化
template <typename T>
class MyPair<T*, T*>
{
public:void print() { cout << "Pointer Pair\n"; }
};// 使用示例
MyPair<int, double> p1; // 通用模板
p1.print(); // 输出 "Generic Pair"MyPair<int, int> p2; // 偏特化(相同类型)
p2.print(); // 输出 "Same Type Pair"MyPair<int*, int*> p3; // 偏特化(指针类型)
p3.print(); // 输出 "Pointer Pair"
3. 特化的匹配规则
精确匹配优先:全特化 > 偏特化 > 通用模板。
编译期决策:特化版本的选择在编译期确定。
声明顺序:特化必须在通用模板之后声明。
三、模板的分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
但是当模板的声明和定义被分离到头文件(.h
)和源文件(.cpp
)时,可能导致链接错误。例如:
// mytemplate.h(头文件)
template <typename T>
class MyTemplate
{
public:void print(T value);
};// mytemplate.cpp(源文件)
#include "mytemplate.h"template <typename T>
void MyTemplate<T>::print(T value)
{std::cout << value << std::endl;
}// main.cpp(使用模板)
#include "mytemplate.h"int main()
{MyTemplate<int> obj;obj.print(10); // 链接错误:未定义 MyTemplate<int>::print(int)
}
根本原因:
模板实例化的时机:模板代码本身不是实际的可执行代码,而是生成代码的“蓝图”。编译器需要在编译阶段看到模板的完整定义,才能根据具体类型实例化出具体的类或函数。
分离编译的局限性:若模板定义在
.cpp
文件中,其他编译单元无法看到完整的模板代码,导致无法实例化模板,链接时找不到具体实现。
解决方案:
- 将模板定义放在头文件中(推荐)
// mytemplate.h
template <typename T>
class MyTemplate {
public:void print(T value) {std::cout << value << std::endl; // 定义直接写在头文件中}
};
优点:
- 简单直接,适用于大多数场景
- 支持任意类型的模板参数
缺点::
- 头文件体积增大,可能增加编译时间
- 暴露实现细节
- 显式实例化
// mytemplate.h
template <typename T>
class MyTemplate
{
public:void print(T value);
};// mytemplate.cpp
#include "mytemplate.h"
template <typename T>
void MyTemplate<T>::print(T value)
{std::cout << value << std::endl;
}// 显式实例化 int 类型的模板
template class MyTemplate<int>; // main.cpp
#include "mytemplate.h"
int main() {MyTemplate<int> obj; // 使用显式实例化的版本obj.print(10); // 正常链接
优点:
隐藏模板实现细节
减少头文件体积
缺点:
需提前知道所有需要实例化的类型,不够灵活
新增类型需修改显式实例化代码
结语
这一篇文章虽说是模板的进阶,但是并没有很难,很多地方的规定在我们学习完前面的知识后是理所当然该这样的。下一篇将会介绍C++中继承相关的知识,有兴趣的朋友可以关注一下。