C++模板学习(进阶)
目录
一.非类型模板参数
二.模板的特化
一).函数模板特化
二).类模板特化
1.全特化
2.偏特化
三.模板分离编译
一).什么是分离编译
1. 问题描述
2. 模板的实例化机制
3. 分离编译的困境
二).解决方法
1. 头文件包含定义(推荐)
2. 显式实例化(不推荐)
这里我们要学习的是模板的进阶用法,对于模板有忘记的可以看下面的文章,可助你的记忆恢复一二:https://blog.csdn.net/a1592266073/article/details/145809809?fromshare=blogdetail&sharetype=blogdetail&sharerId=145809809&sharerefer=PC&sharesource=a1592266073&sharefrom=from_link
一.非类型模板参数
模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
template <class T, int Size> // Size为非类型模板参数(整型常量)
class FixedArray {
private:T data[Size];
public:int getSize() const { return Size; }
};FixedArray<double, 10> arr; // 创建大小为10的数组
限制与注意事项
-
允许的类型:整型、枚举、指针/引用(指向全局对象或静态成员)。
-
必须是编译期常量:值在编译时必须确定。
-
C++20扩展:支持浮点型、字面量类类型(需满足特定条件)。
二.模板的特化
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理。
一).函数模板特化
函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
} // 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}
函数模板只能全特化,但可通过重载实现类似效果
二).类模板特化
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;
};
void TestVector()
{Data<int, int> d1;Data<int, char> d2;
}
2.偏特化
-
定义:针对部分模板参数进行特化。
// 主模板
template <class T, class U>
class MyPair
{ /*...*/
};// 偏特化:当两个类型相同时
template <class T>
class MyPair<T, T>
{ /*...*/
};// 偏特化:当第二个类型为int时
template <classT>
class MyPair<T, int>
{ /*...*/
};
- 参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
template<class T1, class T2>
class Data
{public :Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};//两个参数偏特化为指针类型
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;
};void test2()
{Data<int, double> d2; // 调用基础的模板Data<int*, int*> d3; // 调用特化的指针版本Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}
三.模板分离编译
一).什么是分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
1. 问题描述
-
现象:将模板声明与定义分离到头文件(.h)和源文件(.cpp)时,链接阶段报错。
-
原因:模板代码需在编译时实例化,但分离编译时编译器看不到完整定义。
错误展示:
// mytemplate.h
template <typename T>
class MyTemplate {
public:void doSomething(T value);
};
// mytemplate.cpp
template <typename T>
void MyTemplate<T>::doSomething(T value) { /* 实现 */ }
// main.cpp
#include "mytemplate.h"
int main() {MyTemplate<int> obj;obj.doSomething(5); // 链接错误:未找到实例化代码
}
2. 模板的实例化机制
-
延迟实例化:模板代码在编译时根据具体类型生成实际代码(实例化)。
-
两阶段查找:
-
模板定义阶段:检查模板本身的语法。
-
模板实例化阶段:根据具体类型生成代码。
-
3. 分离编译的困境
-
当模板的声明与定义分离在
.h
和.cpp
文件中时:-
编译器在编译用户代码(如
main.cpp
)时,只能看到模板声明,无法看到定义。 -
导致编译器无法生成实例化代码(如Mytemplate
<int>
的成员函数)。 -
链接时,链接器找不到实例化后的符号,报"undefined reference"错误。
-
二).解决方法
1. 头文件包含定义(推荐)
// mytemplate.h
template <typename T>
class MyTemplate {
public:void doSomething(T value) { // 定义直接写在头文件中// ...实现代码...}
};
2. 显式实例化(不推荐)
// mytemplate.h
template <typename T>
class MyTemplate {
public:void doSomething(T value); // 声明
};// mytemplate.cpp
template <typename T>
void MyTemplate<T>::doSomething(T value) { // 定义// ...实现代码...
}// 显式实例化所需类型
template class MyTemplate<int>; // 生成MyTemplate<int>的所有成员代码
template class MyTemplate<double>;
通过合理使用非类型模板参数、模板特化和正确的代码组织方式,可以充分发挥C++模板的威力,同时保持代码的可维护性和性能。