泛型
[TOC]
1. 函数模板
1
2
3
4
5
template <class T>
void swap(T& a, T& b)
{
/*...*/
}
template <class T>和template <typename T>效果一样。
函数模板,就是建立一个通用函数,其函数类型和形参类型不具体指定,只用一个虚拟的类型T来表示。
在调用时,类型T就会被调用时所给的类型代替。
2. 类模板
模板类本身不是真正的类,只有当指定具体类型参数(如CVSAgent<MyConfig>)时,编译器才会生成一个“真正的类”(称为模板实例)。
模板使用方法与注意点
通过 CVSAgent 与 CTKTwoBuf 这两个类,我们可以学习 C++ 模板的以下知识点:
1. 模板的基本定义
1
2
3
4
5
6
7
8
// 类模板定义
template <class TYPE, class TYPE_SVR>
class CTKTwoBuf {
// 类成员定义...
};
// 使用时需要指定模板参数
CTKTwoBuf<UserConfig, UserService> configBuf;
注意点:
- 模板参数使用
class或typename关键字声明,两者在大多数情况下可以互换 - 类模板的成员函数通常需要在头文件中实现,而不是单独的.cpp文件
- 使用模板类时必须显式指定模板参数类型
2. 模板类中的类型别名
1
2
3
4
5
6
7
// CTKTwoBuf中的函数指针别名
typedef BOOL(TYPE_SVR::*TKUPDATEDICFUNC)(const std::shared_ptr<TYPE>& _pOld,
std::shared_ptr<TYPE>& _pNew);
// C++11及以上也可以使用using关键字
using TKUPDATEDICFUNC = BOOL(TYPE_SVR::*)(const std::shared_ptr<TYPE>& _pOld,
std::shared_ptr<TYPE>& _pNew);
注意点:
- 类型别名可以简化复杂类型的使用,特别是函数指针和迭代器类型
- 在模板类中定义的类型别名依赖于模板参数,使用时需要注意模板实例化的类型
3. 模板类的成员函数实现
1
2
3
4
5
// 模板类成员函数的实现
template <class TYPE, class TYPE_SVR>
BOOL CTKTwoBuf<TYPE, TYPE_SVR>::Init(TYPE_SVR* _pOwner, TKUPDATEDICFUNC _pUpdateDicFunc) {
// 函数实现...
}
注意点:
- 每个成员函数定义前都需要加上
template <class...>声明 - 模板类的成员函数只有在被使用时才会被编译器实例化
- 成员函数的实现通常放在头文件中,否则可能导致链接错误
4. 模板类的静态成员
1
2
3
4
5
6
7
8
9
10
11
// 模板类中静态成员的声明
template <class TYPE>
class CVSAgent {
private:
static CVSAgent<TYPE>* m_pthis;
// ...
};
// 模板类静态成员的定义
template <class TYPE>
CVSAgent<TYPE>* CVSAgent<TYPE>::m_pthis = nullptr;
注意点:
- 每个模板实例化类型都有自己的静态成员实例
- 静态成员需要在类外定义,并且要加上模板参数声明
- 静态成员可以用于实现模板类的单例模式
5. 模板的特化
虽然这两个类没有使用模板特化,但这是一个重要的高级特性:
1
2
3
4
5
6
7
8
9
10
11
// 通用模板
template <class T>
class MyTemplate {
// 通用实现
};
// 模板特化
template <>
class MyTemplate<int> {
// 针对int类型的特殊实现
};
注意点:
- 特化版本必须与通用模板在同一命名空间
- 特化版本可以为特定类型提供优化实现
- 全特化需要指定所有模板参数
6. 使用模板的注意事项
-
代码膨胀:模板会为每个实例化类型生成独立的代码,可能导致可执行文件变大
-
编译时间:使用大量模板会增加编译时间,因为编译器需要为每个实例化类型生成代码
-
错误信息:模板的编译错误信息通常比较冗长和难以理解,需要耐心分析
-
接口约束:模板类对参数类型有隐含约束,例如 CVSAgent 要求 TYPE 有默认构造函数
-
分离编译:模板类的声明和实现通常需要放在同一个头文件中,否则会导致链接错误
-
显式实例化:可以通过显式实例化控制模板生成的代码:
1
2
// 显式实例化
template class CTKTwoBuf<UserConfig, UserService>;
通过这两个类的学习,我们可以看到模板是 C++ 中实现泛型编程的强大工具,能够在保证类型安全的同时提供高度的代码复用。CTKTwoBuf 和 CVSAgent 通过模板实现了对任意数据/配置类型的管理,展示了模板在实际项目中的典型应用。
7. 模板类的单例
模板类的单例是指:针对模板类的每个具体实例化类型(即每个不同的模板参数组合),只允许存在一个类对象。这与普通类的单例不同,普通类的单例是整个类只有一个实例,而模板类的单例是“每个类型一个单例”。
1)为什么模板类可以实现单例?
你提到的“模板需要在编译时确定类型”是正确的,但这恰恰是模板类单例的实现基础:
-
模板的实例化特性:模板类本身不是真正的类,只有当指定具体类型参数(如
CVSAgent<MyConfig>)时,编译器才会生成一个“真正的类”(称为模板实例)。 -
单例与实例绑定:模板类的静态成员(如
m_pthis)会为每个实例化类型单独生成一份。例如:CVSAgent<ConfigA>有自己的静态成员m_pthisCVSAgent<ConfigB>有另一份独立的静态成员m_pthis因此,每个类型的单例是完全隔离的。
2)结合代码理解模板类单例
以CVSAgent为例,其单例实现核心代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <class TYPE>
class CVSAgent {
private:
static CVSAgent<TYPE>* m_pthis; // 静态指针,每个TYPE一份
public:
CVSAgent() {
if (m_pthis != nullptr) {
// 如果已有实例,输出警告
TKWriteLog("警告:旧实例将被覆盖");
}
m_pthis = this; // 当前实例赋值给静态指针
}
};
// 静态成员初始化:每个TYPE单独初始化
template <class TYPE>
CVSAgent<TYPE>* CVSAgent<TYPE>::m_pthis = nullptr;
效果演示:
1
2
3
4
// 实例化两个不同类型的CVSAgent
CVSAgent<ConfigA> agentA; // m_pthis(ConfigA版本)指向agentA
CVSAgent<ConfigB> agentB; // m_pthis(ConfigB版本)指向agentB
CVSAgent<ConfigA> agentA2; // 触发警告,m_pthis(ConfigA版本)改为指向agentA2
agentA和agentB是不同类型的实例,各自的m_pthis互不干扰agentA和agentA2是同一类型,后者会覆盖前者的单例指针
3)模板类单例的关键特点
-
类型隔离:不同模板参数生成的类是完全独立的,单例只在同一类型内生效。
-
编译时确定:单例的数量和类型在编译期就已确定(由模板实例化决定)。
-
静态成员的特殊性:模板类的静态成员属于“实例化后的类”,而非模板本身。这是实现“每个类型一个单例”的核心机制。
4)模板类单例的使用场景
- 管理不同类型的全局配置(如
CVSAgent对每种配置类型单独管理) - 为不同数据类型提供独立的资源池(如针对
int、string的内存池) - 确保同一类型的工具类全局唯一,同时支持多种类型
5)总结
模板类的单例本质是“为每个模板实例化类型单独维护一个单例”,这既利用了模板的泛型能力(支持多种类型),又通过静态成员的实例化特性(每个类型独立)实现了单例的隔离。这种设计在需要为不同类型提供统一管理逻辑,但又要求每种类型全局唯一的场景中非常实用。