这一篇在干嘛?

全书代码都用 C++ 模板写成,而这带来一个工程现实:模板的实现通常必须整个放在头文件里。这个附录解释为什么、有哪些变通,以及”分离编译”在实际项目里的正确姿势。篇幅短,却是把书里的代码编译通过绕不开的一课。

为什么模板难分离

普通类的编译流程:.cpp 编译成目标文件,调用其他模块的函数只需在头文件里看到声明,链接器负责把名字对上——实现可以藏在任何 .cpp 里。

模板不行。vector<int> 的代码在被使用的那个编译单元里现场生成(实例化):编译器必须看到模板的完整定义(包括函数体),才能替你”盖章复制”出 vector<int> 版本的机器码。只在头文件里放声明,编译器两手空空,链接时找不到符号,报出一屏”undefined reference”。

白话:普通函数是”印好的书,各处引用书名”;模板是”印刷模板,谁用谁印刷”——要用就必须把雕版(定义)给人家看。这就是书中所有数据结构代码(Vector.hBinarySearchTree.h……)都把实现写在头文件里的原因。

C++11 的现实:没有 export

C++98 曾设想 export template 关键字让模板像普通函数一样分离,但编译器实现代价巨大,主流编译器几乎没人支持,C++11 中被正式移除。所以标准答案只有一个:

  • 默认做法:模板类 = 一个 .h,声明 + 实现全在里面。
  • 头文件用 #ifndef/#define/#endif 包裹防重复包含。

两个变通方案

方案一:显式实例化。实现放 .cpp,在该文件末尾显式生成需要的类型:

// Stack.cpp —— 实现不公开
template <typename Object>
void Stack<Object>::push(const Object &x) { /* ... */ }
 
template class Stack<int>;       // 只实例化这几个类型
template class Stack<double>;

白话:在实现文件里亲手”印刷”用得上的几个版本。优点是编译快、实现保密(商业库常用);缺点是用户用列表外的类型就直接链接错误——灵活性换工程性。

方案二:#include .cpp 变体。把实现放 .cpp,头文件里 #include "Stack.cpp"。能跑,但破坏 .cpp 的语义约定,容易让构建系统困惑,不推荐。

实践清单

  1. 模板库 = 单头文件(书中 SeparateCompilation 一节的标准答案)。
  2. 大量模板会拖慢编译:公用库可组合”显式实例化 + 常用类型”。
  3. 模板编译错误信息可读性差:从第一条错误看起(后面的多是连锁反应)。

常见坑

把模板成员函数的实现写到 .cpp 里,然后在自己的程序里 vector<MyType> ——链接错误 undefined reference。这是 C++ 新手最常见的困惑之一,原因正是本篇所述”实例化需要看到定义”。

通关标准

能解释:① 为什么模板实现必须进头文件(实例化机制);② 显式实例化的用途与代价;③ export 关键字为什么被移除。