文章

泛型

泛型

[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;

注意点

  • 模板参数使用classtypename关键字声明,两者在大多数情况下可以互换
  • 类模板的成员函数通常需要在头文件中实现,而不是单独的.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. 使用模板的注意事项

  1. 代码膨胀:模板会为每个实例化类型生成独立的代码,可能导致可执行文件变大

  2. 编译时间:使用大量模板会增加编译时间,因为编译器需要为每个实例化类型生成代码

  3. 错误信息:模板的编译错误信息通常比较冗长和难以理解,需要耐心分析

  4. 接口约束:模板类对参数类型有隐含约束,例如 CVSAgent 要求 TYPE 有默认构造函数

  5. 分离编译:模板类的声明和实现通常需要放在同一个头文件中,否则会导致链接错误

  6. 显式实例化:可以通过显式实例化控制模板生成的代码:

1
2
// 显式实例化
template class CTKTwoBuf<UserConfig, UserService>;

通过这两个类的学习,我们可以看到模板是 C++ 中实现泛型编程的强大工具,能够在保证类型安全的同时提供高度的代码复用。CTKTwoBuf 和 CVSAgent 通过模板实现了对任意数据/配置类型的管理,展示了模板在实际项目中的典型应用。

7. 模板类的单例

模板类的单例是指:针对模板类的每个具体实例化类型(即每个不同的模板参数组合),只允许存在一个类对象。这与普通类的单例不同,普通类的单例是整个类只有一个实例,而模板类的单例是“每个类型一个单例”。

1)为什么模板类可以实现单例?

你提到的“模板需要在编译时确定类型”是正确的,但这恰恰是模板类单例的实现基础:

  1. 模板的实例化特性:模板类本身不是真正的类,只有当指定具体类型参数(如CVSAgent<MyConfig>)时,编译器才会生成一个“真正的类”(称为模板实例)。

  2. 单例与实例绑定:模板类的静态成员(如m_pthis)会为每个实例化类型单独生成一份。例如:

    • CVSAgent<ConfigA>有自己的静态成员m_pthis
    • CVSAgent<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
  • agentAagentB是不同类型的实例,各自的m_pthis互不干扰
  • agentAagentA2是同一类型,后者会覆盖前者的单例指针

3)模板类单例的关键特点

  1. 类型隔离:不同模板参数生成的类是完全独立的,单例只在同一类型内生效。

  2. 编译时确定:单例的数量和类型在编译期就已确定(由模板实例化决定)。

  3. 静态成员的特殊性:模板类的静态成员属于“实例化后的类”,而非模板本身。这是实现“每个类型一个单例”的核心机制。

4)模板类单例的使用场景

  • 管理不同类型的全局配置(如CVSAgent对每种配置类型单独管理)
  • 为不同数据类型提供独立的资源池(如针对intstring的内存池)
  • 确保同一类型的工具类全局唯一,同时支持多种类型

5)总结

模板类的单例本质是“为每个模板实例化类型单独维护一个单例”,这既利用了模板的泛型能力(支持多种类型),又通过静态成员的实例化特性(每个类型独立)实现了单例的隔离。这种设计在需要为不同类型提供统一管理逻辑,但又要求每种类型全局唯一的场景中非常实用。

参考文章123456

本文由作者按照 CC BY 4.0 进行授权