Chapter 10 Templates¶
Why templates?
如果你需要一个 X 类型的链表和一个 Y 类型的链表,二者代码逻辑很相似,差别只在于元素类型,那么传统的选择是:
- 复制多份代码:保留类型安全,但难维护
- 提炼公共基类:并不总是合适
- 无类型容器:类型不安全
而模板的核心是用类型作为参数,以泛型编程(generic programming)的方式复用源代码。
1 Basic¶
- 函数模板(Function Templates):用一份代码处理多种类型上的相似操作
示例
普通 swap
void swap(int& x, int& y) {
int temp = x;
x = y;
y = temp;
}
模板版 swap
template <class T>
void swap(T& x, T& y) {
T temp = x;
x = y;
y = temp;
}
template关键字引入模板,class T表示参数化的类型名- 这里的
class并不限于类类型,也可以是内置类型
- 模板实例化(Template Instantiation):编译器根据模板实参替换模板中的类型参数生成新的函数/类定义,随后再进行语法和类型检查,只有真正被使用到的模板版本才会被实例化出来
示例
int i = 3, j = 4;
swap(i, j); // 实例化 int 版本
float k = 4.5, m = 3.7;
swap(k, m); // 实例化 float 版本
std::string s("Hello"), t("World");
swap(s, t); // 实例化 std::string 版本
模板参数要求精确匹配,不做类型转换,即使隐式转换通常也会被忽略。
示例
swap(int, int); // ok
swap(double, double); // ok
swap(int, double); // error
普通函数与模板函数共存时,先找唯一的普通函数精确匹配,再找唯一的模板函数匹配,最后才考虑普通函数的隐式转换。
示例
void f(float i, float k) {}
template <class T>
void f(T t, T u) {}
f(1.0f, 2.0f); // 普通函数
f(1.0, 2.0); // 模板函数
f(1, 2); // 模板函数
f(1, 2.0); // 普通函数
一般编译器会从实参中推导类型,若无法推导,也可以手动显式指定。
示例
template <class T>
void foo() { /* ... */ }
foo<int>(); // type T is int
foo<float>(); // type T is float
2 Class Templates¶
- 类模板(Class Templates):用类型参数化类,常见于容器类,能定义出潜在无限多个不同类
示例:Vector 类模板
template <class T>
class Vector {
public:
Vector(int);
~Vector();
Vector(const Vector&);
Vector& operator=(const Vector&);
T& operator[](int);
private:
T* m_elements;
int m_size;
};
Vector<int> v1(100);
Vector<Complex> v2(256);
v1[20] = 10;
v2[20] = v1[20]; // 若支持 int -> Complex 转换则合法
示例:成员定义
template <class T>
Vector<T>::Vector(int size) : m_size(size) {
m_elements = new T[m_size];
}
template <class T>
T& Vector<T>::operator[](int index) {
if (index < m_size && index >= 0) {
return m_elements[index];
} else {
// ...
}
}
示例:泛型冒泡排序
template <class T>
void sort(Vector<T>& arr) {
const size_t last = arr.size() - 1;
for (int i = 0; i < last; i++) {
for (int j = last; j > i; j--) {
if (arr[j] < arr[j - 1]) {
swap(arr[j], arr[j - 1]);
}
}
}
}
Vector<int> vi(4);
vi[0] = 4; vi[1] = 3; vi[2] = 7; vi[3] = 1;
sort(vi); // 调用 sort(Vector<int>&)
Vector<string> vs(5);
vs[0] = "Fred"; vs[1] = "Wilma"; vs[2] = "Barney"; vs[3] = "Dino"; vs[4] = "Prince";
sort(vs); // 调用 sort(Vector<string>&)
模板算法常依赖元素类型提供相应操作,如这里要求 T 支持 operator<
3 Others¶
- 模板支持多个类型参数。
示例
template <class Key, class Value>
class HashTable {
const Value& lookup(const Key&) const;
void insert(const Key&, const Value&);
};
- 模板可以嵌套(Nesting Templates),其类型参数可以很复杂。
示例
Vector<Vector<double*> >
Vector<int (*)(Vector<double>&, int)>
- 模板参数不一定是类型,也可以是常量表达式,可以带默认值。
- 优点:可能更快。
- 缺点:增加复杂度,类型上带着尺寸信息,可能导致代码膨胀(code bloat)。
示例
非类型模板参数
template <class T, int bounds = 100>
class FixedVector {
public:
FixedVector();
T& operator[](int);
private:
T elements[bounds]; // fixed-size array!
};
成员定义与使用
template <class T, int bounds>
T& FixedVector<T, bounds>::operator[](int i) {
return elements[i];
}
FixedVector<int, 50> v1;
FixedVector<int, 10 * 5> v2;
FixedVector<int> v3; // 使用默认值 100
- 模板类可继承普通类。
示例
template <class A>
class Derived : public Base { /* ... */ };
- 模板类也可继承模板类。
示例
template <class A>
class Derived : public List<A> { /* ... */ };
- 普通类也可继承模板实例。
示例
class SupervisorGroup : public List<Employee*> { /* ... */ };
CRTP: Curiously Recurring Template Pattern
template <class T>
class Base { /* ... */ };
class Derived : public Base<Derived> { /* ... */ };
静态多态
template <class T>
class Base {
public:
void interface() {
static_cast<T*>(this)->implementation();
}
static void static_func() {
T::static_sub_func();
}
};
class Derived : public Base<Derived> {
public:
void implementation();
static void static_sub_func();
};
模板的声明和定义通常都放在头文件里,因为实例化发生在使用点,编译器必须能看到完整定义,若只把定义放进 .cpp,其他翻译单元通常无法完成实例化。模板还能配合 friend 和 static members。
Writing Templates Workflow
- 先写一个非模板版本并跑通
- 建立一套可靠测试用例
- 测性能并调优
- 回看实现,确认哪些类型应参数化
- 再改写成模板
- 用既有测试再次验证