类模板分文件编写

内容简介

模板分文件是 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、使用前置声明等方式控制编译时间。