ARTICLE · 1115742
C++ 高级编程:类模板深度讲解

引言
在 C++ 中,类模板(class template) 是一种强大的泛型编程工具。它本质上是一种 **"规范描述(prescription)"**——规定了如何创建某个类,而这个类中的一个或多个类型或值被参数化。
对于 C++ 初学者来说,完全可以直接使用标准库中的类模板(如 vector、list、map、shared_ptr 等),而无需了解模板定义背后的机制。事实上,本书前面章节中已经多次使用这些类模板,却没有深入讲解模板机制。只有较高级的 C++ 程序员才会定义自己的类模板,并使用本章所述的机制。因此,本章内容属于 C++ 高级话题。
本章分为 入门 与 高级 两部分:
入门部分:介绍如何定义类模板、简单用法、实例化机制,以及如何为类模板定义成员函数、静态数据成员和嵌套类型。 高级部分:面向生产级应用程序开发,包括编译器如何实例化模板、程序组织要求、特化与部分特化、模板中名字的解析规则、命名空间中的类模板定义,最后以一个较大规模的完整示例收尾。
一、入门部分:定义与使用类模板
1.1 为什么需要类模板
假设我们需要一个可以存储任意类型元素的"栈":
classIntStack {
std::vector<int> data;
public:
voidpush(int x){ data.push_back(x); }
intpop(){ int v = data.back(); data.pop_back(); return v; }
boolempty()const{ return data.empty(); }
};
如果还需要 DoubleStack、StringStack……难道要为每种类型都写一遍?显然不现实。类模板正是为解决这类 "类型参数化" 问题而生。
1.2 类模板的定义
template <typename T>
classStack {
public:
voidpush(const T& x);
T pop();
boolempty()const;
private:
std::vector<T> data;
};
要点:
template <typename T>是模板参数列表,T是类型参数(也可写成class T)。在类模板内部, T就像普通类型名一样使用。类模板不是类,而是"生成类的蓝图"。
1.3 成员函数的定义
成员函数可以在类内定义(隐式 inline),也可以在类外定义。类外定义时,必须带上模板参数列表:
template <typename T>
void Stack<T>::push(const T& x) {
data.push_back(x);
}
template <typename T>
T Stack<T>::pop() {
T v = data.back();
data.pop_back();
return v;
}
template <typename T>
bool Stack<T>::empty() const {
return data.empty();
}
注意 Stack<T>:: 前缀——**每个类外成员函数定义前都必须重新声明 template <typename T>**。
1.4 实例化(Instantiation)
使用类模板时,编译器会根据实参类型实例化出一个具体的类:
Stack<int> si; // 实例化 Stack<int>
Stack<std::string> ss; // 实例化 Stack<std::string>
si.push(42);
ss.push("hello");
Stack<int> 与 Stack<std::string> 是两个完全不同的类型,彼此之间没有任何继承或转换关系。
1.5 类模板的成员类型
类模板可以拥有多种成员:
(1)普通成员函数
如上例中的 push、pop、empty。
(2)静态数据成员
template <typename T>
classCounter {
public:
Counter() { ++count; }
~Counter() { --count; }
staticint count;
};
template <typename T>
int Counter<T>::count = 0; // 类外定义静态成员
每个实例化类型 Counter<int>、Counter<double> 各有独立的 count。
(3)嵌套类型
template <typename T>
classContainer {
public:
using value_type = T;
using size_type = std::size_t;
classiterator {/* ... */ };
};
标准库容器(如 vector)正是通过 value_type 等嵌套 typedef 与算法库协作。
(4)成员模板
类模板内还可以再定义模板成员:
template <typename T>
classWrapper {
public:
template <typename U>
voidassign(const U& u){ value = static_cast<T>(u); }
private:
T value{};
};
1.6 一个完整的小例子
#include<vector>
#include<stdexcept>
template <typename T>
classStack {
public:
voidpush(const T& x){ data.push_back(x); }
voidpop(){ data.pop_back(); }
T& top(){ return data.back(); }
boolempty()const{ return data.empty(); }
std::size_tsize()const{ return data.size(); }
private:
std::vector<T> data;
};
intmain(){
Stack<int> s;
s.push(1); s.push(2); s.push(3);
while (!s.empty()) {
std::cout << s.top() << ' ';
s.pop();
}
}
二、高级部分
2.1 编译器如何实例化模板
模板代码具有两阶段名字查找(two-phase lookup) 特性:
定义阶段(模板被解析时):查找不依赖于模板参数的名字。 实例化阶段(模板被实例化时):查找依赖于模板参数的名字(ADL 也参与)。
这意味着:
编译器在遇到使用点时才生成代码。 模板的定义必须对使用点可见——这是模板与普通函数/类最本质的区别。
2.2 程序组织:为什么模板通常放在头文件
由于编译器在实例化时需要看到完整定义,类模板通常 全部写在头文件中:
// stack.hpp
#ifndef STACK_HPP
#define STACK_HPP
template <typename T>
classStack {
public:
voidpush(const T& x){ data.push_back(x); }
// ...
private:
std::vector<T> data;
};
#endif
若坚持分离 .h / .cpp,则需要显式实例化(explicit instantiation):
// stack.cpp
#include"stack.hpp"
templateclassStack<int>;// 显式实例化
templateclassStack<double>;
或使用 extern template 声明抑制重复实例化(C++11):
externtemplateclassStack<int>;// 在其他 TU 中不再实例化
2.3 特化(Specialization)
(1)全特化(Full Specialization)
为某个具体类型提供完全不同的实现:
template <>
classStack<bool> {
public:
voidpush(bool b); // 可能用位压缩实现
boolpop();
private:
std::vector<unsignedchar> bits;
};
全特化是完全独立的类,与原模板无任何隐式关系。
(2)部分特化(Partial Specialization)
只特化部分模板参数,或对参数施加约束:
template <typename T>
classStack<T*> {// 针对所有指针类型
public:
voidpush(T* p){ data.push_back(p); }
T* pop(){ T* p = data.back(); data.pop_back(); return p; }
private:
std::vector<T*> data;
};
template <typename T, typename U>
classPair {/* 主模板 */ };
template <typename T>
classPair<T, T> {/* 当两个类型相同时使用 */ };
(3)成员特化
也可以只特化某个成员函数(但必须是全特化,C++ 不允许成员函数部分特化):
template <>
void Stack<constchar*>::push(constchar* const& s) {
// 处理 C 字符串的特殊逻辑
}
2.4 类模板中的名字解析
模板代码中的名字分为两类:
| 非依赖名 | ||
| 依赖名 |
由于编译器在解析时还不知道 T 是什么,从属类型名必须用 typename 显式标注:
template <typename T>
voidf(){
typename T::value_type* p; // 必须加 typename
T::value_type v; // 也需 typename(此处省略会报错)
}
访问依赖基类成员时,需用 this-> 或 Base<T>:: 限定:
template <typename T>
classDerived :public Base<T> {
public:
voidg(){
this->foo(); // 必须加 this->
Base<T>::bar(); // 或显式限定
}
};
对依赖名调用成员模板时,需用 template 关键字:
template <typename T>
voidh(T& c){
c.template rebind<int>(); // template 消歧
}
2.5 命名空间中的类模板
类模板可以定义在命名空间中,也可对命名空间中的模板做特化(但特化必须与原模板处于同一命名空间):
namespace mylib {
template <typename T>
classVec {/* ... */ };
template <>
classVec<bool> {/* 位压缩特化 */ };
} // namespace mylib
注意:不能向
std命名空间添加新的类模板,只能对已有模板进行特化(且需符合标准规定)。
2.6 一个较大规模的示例:类型安全的矩阵库
#include<vector>
#include<stdexcept>
#include<iostream>
namespace mat {
template <typename T, std::size_t Rows, std::size_t Cols>
classMatrix {
public:
using value_type = T;
staticconstexprstd::size_t rows = Rows;
staticconstexprstd::size_t cols = Cols;
Matrix() : data(Rows * Cols, T{}) {}
T& operator()(std::size_t r, std::size_t c){ return data[r*Cols + c]; }
const T& operator()(std::size_t r, std::size_t c)const{ return data[r*Cols + c]; }
Matrix& operator+=(const Matrix& rhs) {
for (std::size_t i = 0; i < data.size(); ++i)
data[i] += rhs.data[i];
return *this;
}
template <std::size_t K>
Matrix<T, Rows, K> operator*(const Matrix<T, Cols, K>& rhs) const {
Matrix<T, Rows, K> result;
for (std::size_t i = 0; i < Rows; ++i)
for (std::size_t j = 0; j < K; ++j) {
T sum{};
for (std::size_t k = 0; k < Cols; ++k)
sum += (*this)(i,k) * rhs(k,j);
result(i,j) = sum;
}
return result;
}
voidprint()const{
for (std::size_t i = 0; i < Rows; ++i) {
for (std::size_t j = 0; j < Cols; ++j)
std::cout << (*this)(i,j) << ' ';
std::cout << '\n';
}
}
private:
std::vector<T> data;
};
// 部分特化:1×N 行向量
template <typename T, std::size_t N>
classMatrix<T, 1, N> {
public:
// 不同的实现,例如可省略部分运算
};
} // namespace mat
intmain(){
mat::Matrix<double, 2, 3> A;
mat::Matrix<double, 3, 2> B;
for (std::size_t i = 0; i < 2; ++i)
for (std::size_t j = 0; j < 3; ++j) A(i,j) = i + j;
for (std::size_t i = 0; i < 3; ++i)
for (std::size_t j = 0; j < 2; ++j) B(i,j) = i - j;
auto C = A * B; // Matrix<double, 2, 2>
C.print();
}
该示例展示了类模板的诸多高级特性:
非类型模板参数: std::size_t Rows, Cols。静态常量成员: rows、cols。成员模板: operator*返回维度由参数决定的新类型。部分特化: Matrix<T,1,N>。命名空间封装。
三、小结
template <typename T> class X { ... }; | |
template<>;部分特化 template<...> class X<...> | |
typename / template / this-> | |
类模板是 C++ 泛型编程的基石,STL 的容器、迭代器、算法几乎全部建立在它之上。掌握类模板的定义、实例化、特化及名字解析规则,是从"使用 STL"迈向"编写可复用库"的关键一步。真正的功力,在于设计出接口清晰、约束得当、编译期可诊断的模板——这正是高级 C++ 程序员的核心竞争力。