类模板是C++泛型编程的核心工具之一,它允许我们定义能够处理多种数据类型的类结构。其基本语法格式如下:
cpp复制template<class T1, class T2> // 模板参数列表
class ClassName { // 类声明
public:
ReturnType functionName(T1 var1, T2 var2); // 成员函数声明
T1 memberVar1; // 成员变量
T2 memberVar2;
};
// 类外成员函数定义
template<class T1, class T2>
ReturnType ClassName<T1,T2>::functionName(T1 var1, T2 var2) {
// 函数实现
}
使用类模板时,必须显式指定模板参数类型:
cpp复制ClassName<string, int> obj; // 创建具体化的类实例
obj.functionName("test", 42);
注意:与函数模板不同,类模板不支持类型自动推导,必须显式指定所有模板参数类型(除非有默认参数)
类模板与函数模板有两个重要区别:
类型推导:函数模板可以根据实参自动推导模板参数类型,而类模板必须显式指定
cpp复制// 函数模板示例 - 支持自动推导
template<typename T>
void func(T arg) {} // 调用时 func(42) 能自动推导T为int
// 类模板示例 - 必须显式指定
template<typename T>
class MyClass {};
MyClass<int> obj; // 必须明确指定类型
默认参数:类模板允许模板参数有默认值,而函数模板在C++17之前不支持
cpp复制template<class T1, class T2 = int> // T2默认为int
class DefaultParams {
// 类定义
};
DefaultParams<string> obj; // 等价于DefaultParams<string, int>
类模板的成员函数采用"延迟实例化"机制,这意味着:
cpp复制template<typename T>
class LazyInstantiation {
public:
void validFunc() { /* 正确实现 */ }
void errorFunc() { return "string"; } // 这里故意写错返回类型
};
int main() {
LazyInstantiation<int> obj;
obj.validFunc(); // 只实例化validFunc
// obj.errorFunc(); // 如果注释掉这行,程序能编译通过
}
当类模板作为函数参数时,有三种典型的使用模式:
指定具体类型 - 最直接的方式,但灵活性最低
cpp复制void processPerson(Person<string, int>& p) {
p.showInfo();
}
参数模板化 - 保持与类模板相同的参数
cpp复制template<class T1, class T2>
void processPerson(Person<T1, T2>& p) {
p.showInfo();
cout << "T1 type: " << typeid(T1).name() << endl;
}
整体模板化 - 最灵活的方式,适用于需要处理多种模板类的情况
cpp复制template<class T>
void processAny(T& obj) {
obj.showInfo(); // 要求T必须有showInfo方法
}
实际开发建议:优先考虑第二种方式,它在类型安全和灵活性之间取得了良好平衡。第三种方式虽然灵活但会丢失类型信息,应谨慎使用。
类模板的继承比普通类更复杂,主要有两种场景:
子类指定父类具体类型 - 简单但不够灵活
cpp复制template<class T>
class Base { /*...*/ };
class Derived : public Base<int> { // 明确指定父类类型
// 实现
};
子类保持模板特性 - 更灵活但实现更复杂
cpp复制template<class T1, class T2>
class Derived : public Base<T2> { // 继承模板化的父类
public:
T1 additionalMember;
void extendedMethod() {
// 可以同时使用T1和T2
}
};
在大型项目中使用模板继承时,需要注意:
将类模板成员函数定义在类外部时,语法有特殊要求:
cpp复制// 类定义
template<class T1, class T2>
class ExternalImpl {
public:
void memberFunc(T1 param);
};
// 类外成员函数定义
template<class T1, class T2>
void ExternalImpl<T1,T2>::memberFunc(T1 param) {
// 实现细节
}
关键注意事项:
类模板的声明和实现通常放在.hpp文件中,这是因为:
推荐的文件结构:
code复制project/
├── include/
│ └── my_template.hpp // 包含声明和实现
└── src/
└── main.cpp // 使用模板的代码
对于大型模板类,可以采用以下方式保持代码清晰:
cpp复制// my_template.hpp
#pragma once
// 前置声明
template<class T> class MyClass;
// 接口声明
template<class T>
class MyClass {
public:
void publicMethod();
private:
void privateMethod();
};
// 实现部分(通常在文件后部)
template<class T>
void MyClass<T>::publicMethod() {
// 实现
}
// 更多实现...
类模板的友元机制比普通类更复杂,主要有两种实现方式:
类内实现友元函数 - 简单直接
cpp复制template<class T1, class T2>
class Person {
friend void printPerson(Person<T1,T2> p) {
cout << p.name << ":" << p.age << endl;
}
private:
T1 name;
T2 age;
};
类外实现友元函数 - 更灵活但需要前置声明
cpp复制// 前置声明
template<class T1, class T2> class Person;
// 友元函数声明
template<class T1, class T2>
void printPerson(Person<T1,T2> p);
// 类定义
template<class T1, class T2>
class Person {
friend void printPerson<>(Person<T1,T2> p);
private:
T1 name;
T2 age;
};
// 友元函数实现
template<class T1, class T2>
void printPerson(Person<T1,T2> p) {
cout << p.name << ":" << p.age << endl;
}
工程经验:在模板元编程中,友元机制常用于实现自定义类型转换或序列化操作。类内实现适合简单场景,类外实现更适合复杂项目。
C++11/14/17对类模板做了多项改进:
模板别名 (using):比typedef更直观
cpp复制template<typename T>
using Vec = std::vector<T, MyAllocator<T>>;
Vec<int> numbers; // 等价于std::vector<int, MyAllocator<int>>
变量模板 (C++14):模板化的常量
cpp复制template<class T>
constexpr T pi = T(3.1415926535897932385);
float f = pi<float>; // 3.14159f
double d = pi<double>; // 3.141592653589793
折叠表达式 (C++17):简化可变参数模板
cpp复制template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 折叠表达式
}
缺少模板参数:
cpp复制MyTemplate obj; // 错误:缺少模板参数
MyTemplate<int> obj; // 正确
链接错误:
症状:undefined reference to
MyClass<int>::method()
原因:模板实现放在.cpp文件且未显式实例化
解决:将实现移到.hpp或显式实例化所需类型
模板参数不匹配:
cpp复制template<class Container>
void process(Container& c) {
c.push_back(1); // 错误:并非所有容器都有push_back
}
模板会导致编译器为每种类型组合生成独立代码,可能造成:
优化策略:
显式实例化:限制支持的模板参数组合
cpp复制// 在.cpp文件中
template class MyTemplate<int>; // 显式实例化int版本
template class MyTemplate<string>; // 显式实例化string版本
使用公共基类:将通用功能提取到非模板基类
cpp复制class Base {
public:
virtual void commonMethod() = 0;
};
template<class T>
class Derived : public Base {
public:
void commonMethod() override { /*...*/ }
};
类型擦除技术:使用std::function、std::any等
SFINAE (Substitution Failure Is Not An Error):
cpp复制template<class T>
auto print(const T& val) -> decltype(val.toString(), void()) {
cout << val.toString();
}
template<class T>
void print(const T& val) { // 后备实现
cout << val;
}
类型萃取:
cpp复制template<class T>
void process(T val) {
if constexpr (std::is_pointer_v<T>) {
// 处理指针类型
} else {
// 处理非指针类型
}
}
CRTP (Curiously Recurring Template Pattern):
cpp复制template<class Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
class MyClass : public Base<MyClass> {
public:
void implementation() { /*...*/ }
};
在实际项目中,我发现合理使用类模板可以大幅提高代码复用率,但过度使用会导致代码可读性下降。一个好的经验法则是:当发现自己在复制粘贴类定义只为了改变数据类型时,就应该考虑使用类模板了。