类模板分文件编写
内容简介
模板分文件是 C++ 初学者经常遇到的链接问题。普通函数可以把声明放 .h、定义放 .cpp,但模板通常不能这样简单拆分。
为什么模板通常放头文件
模板不是普通函数定义。编译器需要在实例化模板时看到完整定义,才能根据具体类型生成代码。
如果 Box.h 只有声明,Box.cpp 里放模板定义,另一个 .cpp 使用 Box<int> 时,编译器可能看不到定义,最终链接失败。
错误写法
Person.h:
#pragma once
template <typename Name, typename Age>
class Person {
public:
Person(Name name, Age age);
void show() const;
private:
Name name_;
Age age_;
};Person.cpp:
#include "Person.h"
template <typename Name, typename Age>
Person<Name, Age>::Person(Name name, Age age)
: name_(name), age_(age)
{
}
template <typename Name, typename Age>
void Person<Name, Age>::show() const
{
std::cout << name_ << " " << age_ << '\n';
}如果其他 .cpp 使用 Person<std::string, int>,可能出现模板定义不可见导致链接失败。
推荐写法一:全部放头文件
#pragma once
#include <iostream>
#include <string>
template <typename Name, typename Age>
class Person {
public:
Person(Name name, Age age)
: name_(name), age_(age)
{
}
void show() const
{
std::cout << name_ << " " << age_ << '\n';
}
private:
Name name_;
Age age_;
};推荐写法二:声明放 .h,实现放 .hpp
Person.h:
#pragma once
#include <string>
template <typename Name, typename Age>
class Person {
public:
Person(Name name, Age age);
void show() const;
private:
Name name_;
Age age_;
};
#include "Person.hpp"Person.hpp:
#pragma once
#include <iostream>
template <typename Name, typename Age>
Person<Name, Age>::Person(Name name, Age age)
: name_(name), age_(age)
{
}
template <typename Name, typename Age>
void Person<Name, Age>::show() const
{
std::cout << name_ << " " << age_ << '\n';
}显式实例化
如果只允许固定类型使用模板,可以在 .cpp 中显式实例化:
template class Person<std::string, int>;这种方式适合库内部控制类型集合,不适合开放式通用模板。
最佳代码实践
- 通用模板优先放头文件或
.hpp。 .hpp常用于放模板实现,并被.h包含。- 如果模板只支持少数固定类型,可以考虑显式实例化。
- 不要把模板实现藏在普通
.cpp里再期待所有类型都能使用。
错误用法
// Person.h 只有模板声明
// Person.cpp 有模板定义
// main.cpp 使用 Person<double, int>问题:main.cpp 实例化时看不到模板定义,链接阶段可能找不到符号。
注意事项
- 模板放头文件会增加编译依赖,这是 C++ 模板常见代价。
- 大型项目要通过合理拆分头文件、减少 include、使用前置声明等方式控制编译时间。