C++ 編譯模型基於獨立編譯單元 + 鏈接符號解析。普通函數在編譯期生成機器碼,鏈接期僅修補地址。但模板是「代碼模具」,編譯器看到模板定義時不生成任何二進制代碼,只有當具體模板參數(如 MyClass)被使用時,才會實例化出實體代碼。
// A.h
template void f(T) {}
// A.cpp
#include "A.h" // 僅包含聲明,無實例化調用
// main.cpp
#include "A.h"
int main() { f(10); } // 需要 f
A.cpp 編譯:編譯器見到模板定義,但無 f 調用 → 不實例化 → 目標文件 A.obj 無 f 符號。main.cpp 編譯:見到聲明與調用 f(10) → 標記外部引用 f,期待鏈接器解析。A.obj 符號導出表找不到 f → 鏈接錯誤 LNK2019。// B.h
void g(int);
// B.cpp
#include "B.h"
void g(int x) { /* 實現 */ }
// main.cpp
#include "B.h"
int main() { g(10); }
B.cpp 編譯生成 g 機器碼 → B.obj 導出表含 g 地址。main.cpp 編譯標記外部引用 g。B.obj 找到 g 地址 → 修補 main.obj 調用指令 → 成功。C++98 標準曾引入 export 關鍵字支持分離編譯,但實現極其複雜(需編譯器在鏈接期訪問模板定義、跨編譯單元實例化),僅 Comeau C++ 完整實現。C++11 棄用 export,C++20 移除。現代編譯器(GCC、Clang、MSVC)均不支持。
| 方案 | 原理 | 優點 | 缺點 |
|---|---|---|---|
| 包含模型 | 實現寫在 .h 或 .h 包含 .cpp | 簡單、通用 | 編譯時間長、目標文件膨脹 |
| 顯式實例化 | .cpp 末尾 template class Foo | 控制實例化類型、分離編譯 | 需預知所有用到的類型 |
| extern template (C++11) | 聲明外部實例化,配合顯式實例化 | 避免重複實例化、加快編譯 | 需協調聲明與定義位置 |
C++ 分離編譯機制要求符號在編譯期確定地址,而模板實例化推遲到使用點。這一根本矛盾導致分離編譯不可行。理解「編譯期只見聲明、鏈接期需定義」與「模板使用時才實例化」的衝突,是掌握 C++ 模板工程化的關鍵。