c++ template in .h and .cpp
一、根本原则:模板实例化点必须能看到其完整定义
编译器在看到
Foo<int>
这样的用法时,必须立即拿到template<class T> class Foo { … };
的完整定义来生成代码(隐式实例化)。如果实例化时看不到定义,就只能等到链接阶段去找预先“烤”好的显式实例化对象(
extern template
/template class Foo<int>;
)。
因此,决定“把模板定义写在 .h 还是 .cpp”的关键就是:你希望使用者隐式实例化,还是只允许显式实例化你列出的那些类型。
二、两种常见组织方式
组织方式 | .h 文件内容 | .cpp 文件内容 | 适用场景 | 优点 | 缺点 |
---|---|---|---|---|---|
全部写在 .h(最常见) | 模板声明 + 定义 | 无 | 需要支持任何类型的隐式实例化 | * 使用简单:包含头文件即用** 满足库模板(std::vector 等)需求* | * 每个翻译单元都会复制一份代码,编译时间和二进制体积膨胀* |
声明在 .h,定义在 .cpp,并显式实例化 | 模板声明可选加 extern template class Foo<int>; 抑制再次生成 | 模板定义template class Foo<int>; template class Foo<double>; | 只打算支持固定若干类型 | * 仅编译一份机器码,缩短链接时间、减小可执行文件** 隐藏实现细节,减少头文件依赖* | * 只能用你显式列出的类型;漏掉类型就会“undefined reference”* |
三、关键语法回顾
// === Foo.h ===
#pragma once
template<typename T>
class Foo {
public:Foo(T v); // 仅声明void bar();
};
extern template class Foo<int>; // 可选:阻止其他翻译单元再次隐式实例化 Foo<int>// === Foo.cpp ===
#include "Foo.h"template<typename T>
Foo<T>::Foo(T v) { /*...*/ }template<typename T>
void Foo<T>::bar() { /*...*/ }// 显式实例化
template class Foo<int>;
template class Foo<double>;
四、两种策略的深度对比
维度 | .h 全定义 | .cpp + 显式实例化 |
---|---|---|
使用灵活性 | 最高,支持任意 T | 受限于你列出的类型 |
模板库依赖 | 头文件包含链可能爆炸 | 使用者只依赖声明,隔离编译单元 |
编译时间 | N 个 .cpp × 同一份模板代码 | 只编译一次,快 |
可执行体积 | 链接器能合并重复模板,但仍可能大 | 最小 |
ABI 稳定性 | 与头文件高度耦合,改一行全项目重编 | 实现隐藏,头文件稳定 |
调试友好性 | 调试器可在调用处看到源 | 需要跳到库 .cpp |