将模板类的声明和实现分别放在不同的文件中

本文解释了为什么模板不能像普通函数那样仅在头文件中声明而在源文件中定义的原因。介绍了编译器处理模板的方式,并提供了两种解决方案:一是通过.tpp文件包含模板实现;二是显式实例化模板。

参考:
http://www.cs.technion.ac.il/users/yechiel/c+±faq/templates-defn-vs-decl.html
https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file

如果像一般函数那样处理,简单地将模板声明、定义分别放在 .h 和 .cpp 文件中,会报错“无法解析的外部命令”。



错误原因


3个前提:

  1. 模板不是类或函数,模板是编译器用来生成一系列(a family of)类或函数的模式(pattern)。
  2. 编译器为了生成特定代码,它必须知道模板的定义、声明,以及特定的、用于“填充模板中待定类型”的具体类型。
    例如,为了使用 Foo<int>,编译器必须同时“看到”Foo模板和 Foo<int>.
  3. 编译器在编译一个cpp文件时,不了解另一个cpp文件里的情况。

例子:

//Foo.h
template<typename T>
class Foo 
{
private:
 	T x;
public:
	 Foo();
	 void someMethod(T x);

};

//Foo.cpp
template<typename T>
Foo<T>::Foo()
{
 	...
}
template<typename T>
void Foo<T>::someMethod(T x)
{
 	...
}

// Bar.cpp
void blah_blah_blah()
{
	...
 	Foo<int> f;
 	f.someMethod(5);
 	...
}
  • 在Foo.cpp中编译器只看到模板,不知道该用什么类型来填充“T”。
  • 在 Bar.cpp中,编译器只看到它需要创建一个Foo<int>,但是看不到模板,不知道如何生成代码。

综上所述,编译器无法同时看到Foo模板和Foo<int>实例,所以无法生成函数 Foo<int>::someMethod()


所以说,“将模板声明和定义放在一起”,实际上是为了达成 “编译器能同时“看到”生成代码的模板和用于填充模板的具体类型” 这一目的。



解决方法

  1. 创建一个同名的.tpp后缀文件,将模板类的实现放入其中,然后在同名.h文件末尾#include。
    代码:
//Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

//Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};
#include "Foo.tpp"

原文中的回答没有提到“.tpp”后缀的意义,这里我认为是为了能使文件命名一致而使用的后缀,起到的作用和.h一样。

  1. 在.cpp文件中加上具体的类型
    代码:
// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

这种方法为模板的使用加上了限制,只能使用 int 和 float 实现 Foo 模板类。

C++ 中,模板类的定义实现通常需要放在文件中,因为编译器需要在编译时看到完整的模板实现,以便根据模板参数进行实例化 [^1]。然而,在某些情况下,开发者希望将模板的声明实现分离到不同文件中,以提高代码的可维护性模块化。实现这种分离需要采用特定的组织方法。 ### 类模板声明实现分离方法 模板类声明通常放在文件中,而实现则可以放在单独的源文件中,但需要使用 `#include` 指令将实现文件包含在头文件末尾,或者在使用模板类的源文件中直接包含实现文件。这种方式确保了编译器能够访问模板的完整实现,并在需要时生成特定类型的实例。 #### 示例:模板类声明实现分离 **头文件 `MyTemplate.h`** ```cpp #ifndef MY_TEMPLATE_H #define MY_TEMPLATE_H template <class T1, class T2> class MyClass { private: T1 m_val1; T2 m_val2; public: MyClass(T1 val1, T2 val2); MyClass(T1 val1); void show(); }; #include "MyTemplate.cpp" // 包含实现文件 #endif // MY_TEMPLATE_H ``` **实现文件 `MyTemplate.cpp`** ```cpp template <class T1, class T2> MyClass<T1, T2>::MyClass(T1 val1, T2 val2) : m_val1(val1), m_val2(val2) { } template <class T1, class T2> MyClass<T1, T2>::MyClass(T1 val1) : m_val1(val1) { } template <class T1, class T2> void MyClass<T1, T2>::show() { std::cout << this->m_val1 << " 年薪 " << this->m_val2 << " 万" << std::endl; } ``` **主程序文件 `main.cpp`** ```cpp #include "MyTemplate.h" int main() { MyClass<int, double> obj(100, 50.5); obj.show(); // 输出: 100 年薪 50.5 万 return 0; } ``` ### 模板实例化的显式声明 另一种方法是显式声明模板的实例化类型。这种方式适用于已知模板类将被使用的具体类型。在实现文件中,使用 `template class` 语法显式实例化模板类,确保编译器在链接阶段可以找到对应的实现。 **修改 `MyTemplate.cpp` 文件** ```cpp template <class T1, class T2> MyClass<T1, T2>::MyClass(T1 val1, T2 val2) : m_val1(val1), m_val2(val2) { } template <class T1, class T2> MyClass<T1, T2>::MyClass(T1 val1) : m_val1(val1) { } template <class T1, class T2> void MyClass<T1, T2>::show() { std::cout << this->m_val1 << " 年薪 " << this->m_val2 << " 万" << std::endl; } // 显式实例化 template class MyClass<int, double>; ``` **头文件 `MyTemplate.h`** ```cpp #ifndef MY_TEMPLATE_H #define MY_TEMPLATE_H template <class T1, class T2> class MyClass { private: T1 m_val1; T2 m_val2; public: MyClass(T1 val1, T2 val2); MyClass(T1 val1); void show(); }; #endif // MY_TEMPLATE_H ``` **主程序文件 `main.cpp`** ```cpp #include "MyTemplate.h" int main() { MyClass<int, double> obj(100, 50.5); obj.show(); // 输出: 100 年薪 50.5 万 return 0; } ``` ### 文件组织建议 1. **头文件(`.h`)**:仅包含模板类声明。 2. **实现文件(`.cpp`)**:包含模板类成员函数定义,并在末尾使用 `#include` 指令将实现文件包含到头文件中,或显式实例化模板类。 3. **主程序文件(`.cpp`)**:包含头文件并使用模板类。 这种方式确保了代码的清晰组织,同时满足编译器对模板实例化的需求 [^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值