类模板 (Class Templates)
C++ 类模板 (Class Templates)
类模板允许定义通用的类,支持多种数据类型,是 静态多态(Static Polymorphism) 的核心机制之一。需要先了解函数模版, 再通过以下代码示例和底层原理,详细了解类模板的定义、实例化、特化及其高级用法。
1. 类模板基础
1.1 定义与语法
类模板通过 template
关键字声明,使用类型参数 T
表示泛型类型:
template <typename T>
class Box {
private:T content;
public:Box(T content) : content(content) {}T getContent() const { return content; }
};
typename T
表示类型参数(也可用class T
,二者等价)。- 模板参数可定义多个(如
template <typename T, int N>
)。
1.2 实例化类模板
编译器根据使用的类型 隐式实例化 具体类:
int main() {Box<int> intBox(42); // 隐式实例化为 Box<int>Box<string> strBox("Hello"); // 隐式实例化为 Box<string>cout << intBox.getContent() << endl; // 42cout << strBox.getContent() << endl; // Helloreturn 0;
}
1.3 底层原理
- 编译器为每个类型生成独立类(如
Box<int>
和Box<string>
)。 - 符号表中生成唯一名称(Name Mangling),例如:
Box<int>
→_3BoxIiE
Box<string>
→_3BoxINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE
查看符号名(Linux/g++):
g++ -c main.cpp -o main.o
nm main.o | grep Box
输出示例:
00000000 W _ZN3BoxIiEC1Ei # Box<int>::Box(int)
00000000 W _ZN3BoxIiE10getContentEv # Box<int>::getContent()
W: 弱符号(Weak Symbol),允许重复定义,链接时合并
2. 成员函数的外部定义
2.1 类内定义
成员函数直接在类内定义时,默认是内联的:
template <typename T>
class Box {
public:Box(T content) : content(content) {}T getContent() const { return content; } // 类内定义
private:T content;
};
2.2 类外定义
若成员函数在类外定义,需显式声明模板参数:
template <typename T>
class Box {
public:Box(T content);T getContent() const;
private:T content;
};// 构造函数外部定义
template <typename T>
Box<T>::Box(T content) : content(content) {}// 成员函数外部定义
template <typename T>
T Box<T>::getContent() const { return content; }
3. 类模板特化
3.1 全特化 (Full Specialization)
为特定类型提供定制实现:
// 通用模板
template <typename T>
class Box {
public:string getType() const { return "Generic Box"; }
};// 全特化(针对 double)
template <>
class Box<double> {
public:string getType() const { return "Double Box"; }
};int main() {Box<int> intBox; // 使用通用模板Box<double> dblBox; // 使用全特化版本cout << intBox.getType() << endl; // "Generic Box"cout << dblBox.getType() << endl; // "Double Box"return 0;
}
3.2 偏特化 (Partial Specialization)
对部分模板参数进行特化:
// 通用模板
template <typename T, typename U>
class Pair {
public:string getType() const { return "Generic Pair"; }
};// 偏特化(当两个类型相同时)
template <typename T>
class Pair<T, T> {
public:string getType() const { return "Same Type Pair"; }
};int main() {Pair<int, double> p1; // 通用模板Pair<int, int> p2; // 偏特化版本cout << p1.getType() << endl; // "Generic Pair"cout << p2.getType() << endl; // "Same Type Pair"return 0;
}
4. 静态成员与类模板
静态成员在每个实例化的类中独立存在:
template <typename T>
class Counter {
public:static int count;Counter() { count++; }
};// 初始化静态成员
template <typename T>
int Counter<T>::count = 0;int main() {Counter<int> a, b;Counter<double> c;cout << Counter<int>::count << endl; // 2cout << Counter<double>::count << endl; // 1return 0;
}
5. 高级主题
5.1 友元函数与类模板
在类模板中声明友元函数:
template <typename T>
class Box {
private:T content;
public:Box(T content) : content(content) {}// 声明友元函数(需为模板)friend ostream& operator<<(ostream& os, const Box<T>& box) {os << box.content;return os;}
};int main() {Box<int> box(42);cout << box << endl; // 42return 0;
}
5.2 变长模板参数(C++11)
支持任意数量的模板参数:
template <typename... Args>
class Tuple {};int main() {Tuple<int, double, string> t;return 0;
}
5.3 类型别名(C++11 using)
简化模板类名:
template <typename T>
using Vec = std::vector<T>;int main() {Vec<int> numbers = {1, 2, 3};return 0;
}
6. 两阶段编译与错误检查
6.1 阶段一(模板定义阶段)
- 检查语法和非依赖名称。
- 例如,模板中的语法错误(如
int x = "error";
)直接报错。
6.2 阶段二(模板实例化阶段)
- 生成具体类代码,检查依赖名称。
- 例如,调用未定义的成员函数
T::foo()
,仅在实例化时报告错误。
7. 显式实例化控制
减少重复编译开销:
// 显式实例化声明(头文件)
extern template class Box<int>;// 显式实例化定义(源文件)
template class Box<int>;
8. 总结
8.1 核心优势
- 代码复用:同一逻辑适用于不同类型。
- 类型安全:避免
void*
等不安全操作。 - 性能:编译时生成代码,无运行时开销。
8.2 适用场景
- 容器类(如
std::vector<T>
)。 - 数学库(如矩阵运算)。
- 工厂模式(创建泛型对象)。
8.3 注意事项
- 代码膨胀:过多实例化增加二进制体积。
- 编译时间:复杂模板增加编译时间。
- 错误信息:模板错误信息可能难以理解。
多选题
题目 1:类模板的成员函数惰性实例化
以下代码是否能编译通过?为什么?
template <typename T>
class DataProcessor {
public:void processA(T value) { value.foo(); } // T 需要 foo()void processB(T value) { value.bar(); } // T 需要 bar()
};struct ValidType {void foo() {}void bar() {}
};struct PartialType {void foo() {}// 缺少 bar()
};int main() {DataProcessor<ValidType> dp1;dp1.processA(ValidType{}); // 调用 processADataProcessor<PartialType> dp2;// dp2.processB(PartialType{}); // 若取消注释,结果如何?return 0;
}
A. 编译成功,所有成员函数默认生成
B. 编译失败,DataProcessor<PartialType>
未定义 processB
C. 编译成功,但取消注释后因 PartialType
缺少 bar()
报错
D. 编译失败,模板类必须完全合法
题目 2:全特化与偏特化的优先级
以下代码的输出是什么?
template <typename T>
class Adapter {
public:void describe() { cout << "Generic Adapter" << endl; }
};template <typename T>
class Adapter<T*> {
public:void describe() { cout << "Pointer Adapter" << endl; }
};template <>
class Adapter<int*> {
public:void describe() { cout << "Int Pointer Adapter" << endl; }
};int main() {Adapter<float*> p1;Adapter<int*> p2;p1.describe();p2.describe();return 0;
}
A. Pointer Adapter
和 Int Pointer Adapter
B. Generic Adapter
和 Int Pointer Adapter
C. Pointer Adapter
和 Generic Adapter
D. 编译失败,存在歧义
题目 3:静态成员与模板实例化
以下代码的输出是什么?
template <typename T>
class Tracker {
public:static int count;Tracker() { count++; }~Tracker() { count--; }
};template <typename T>
int Tracker<T>::count = 0;int main() {Tracker<int> t1, t2;{Tracker<double> t3;cout << Tracker<int>::count << " " << Tracker<double>::count << " ";}cout << Tracker<int>::count << endl;return 0;
}
A. 2 1 2
B. 2 1 1
C. 2 1 0
D. 2 0 2
题目 4:友元函数与模板参数关联
以下代码是否能编译通过?为什么?
template <typename T>
class Wrapper {
private:T data;
public:Wrapper(T d) : data(d) {}// 友元声明friend void debugPrint(Wrapper<T> w);
};template <typename T>
void debugPrint(Wrapper<T> w) {cout << "Data: " << w.data << endl;
}int main() {Wrapper<int> w(42);debugPrint(w);return 0;
}
A. 编译成功,输出 Data: 42
B. 编译失败,debugPrint
未声明为模板函数
C. 编译失败,友元函数无法访问 data
D. 链接失败,debugPrint
未定义
题目 5:模板类与动态多态的交互
以下代码的输出是什么?
template <typename T>
class Base {
public:virtual void print() { cout << "Base<T>" << endl; }
};template <>
class Base<int> {
public:virtual void print() { cout << "Base<int>" << endl; }
};class Derived : public Base<int> {
public:void print() override { cout << "Derived" << endl; }
};int main() {Base<int>* b = new Derived();b->print();delete b;return 0;
}
A. Base<T>
B. Base<int>
C. Derived
D. 编译失败,模板类不能全特化
答案与解析
题目 1:类模板的成员函数惰性实例化
答案:C
解析:
- 类模板的成员函数仅在调用时实例化(惰性实例化)。
DataProcessor<ValidType>
的processA
被调用,合法。DataProcessor<PartialType>
的processB
未被调用,因此即使PartialType
缺少bar()
,只要不调用processB
,代码仍可编译。若取消注释,实例化processB
时会因bar()
缺失报错。
题目 2:全特化与偏特化的优先级
答案:A
解析:
- 优先级规则:全特化 > 偏特化 > 通用模板。
Adapter<float*>
匹配偏特化T*
,输出Pointer Adapter
。Adapter<int*>
匹配全特化,输出Int Pointer Adapter
。
题目 3:静态成员与模板实例化
答案:B
解析:
- 静态成员在每个模板实例中独立存在。
Tracker<int>
实例化两次(t1
,t2
),count
为 2。Tracker<double>
实例化一次(t3
),离开作用域后析构,count
变为 0。- 输出:
2 1 2
(内部作用域)→ 离开作用域后Tracker<int>::count
仍为 2?
更正:题目设计有误,正确输出应为2 1 2
(选项 A),但析构后count--
,最终Tracker<int>::count
应为 2(两次构造,两次析构)。实际答案为 A。
题目 4:友元函数与模板参数关联
答案:D
解析:
- 友元函数
debugPrint
声明为非模板函数,但实际定义是模板函数,导致链接时找不到非模板版本的debugPrint
。 - 正确做法:在类内声明友元函数为模板:
template <typename U> friend void debugPrint(Wrapper<U> w);
题目 5:模板类与动态多态的交互
答案:C
解析:
Base<int>
是全特化版本,Derived
继承并重写print()
。- 通过基类指针调用虚函数,触发动态绑定,输出
Derived
。
总结
这些题目覆盖了类模板的惰性实例化、特化优先级、静态成员、友元函数和继承多态等高级主题。通过分析这些场景,可以深入理解模板的静态多态机制及其与动态多态的交互,从而编写更健壮的泛型代码。