为什么C++模板只能在头文件中实现

127 阅读2分钟

为什么C++模板只能在头文件中实现

答案:模板的实现并非必须在头文件中。

bug再现:当我尝试将模板的定义和实现分别保存在头文件(Foo.h)和实现文件(Foo.cpp)中时,程序在链接时报错:

错误	LNK2019	无法解析的外部符号 "public: void __cdecl Foo<int>::doSomething(int)" (?doSomething@?$Foo@H@@QEAAXH@Z),函数 main 中引用了该符号

以下是头文件和实现文件内容:

Foo.h

template <typename T>
struct Foo
{
 void doSomething(T param);   
}

Foo.cpp

template <typename T>
void Foo<T>::doSomething(T param){
    
}

以上代码出错的原因在于,当实例化一个模板时,编译器会根据给定的模板参数创建一个新类,对于上述代码,我们在main.cpp中对Foo进行实例化:

//main.cpp
int main(){
    Foo<int>f;
}

当读取到 Foo<int>f;这一行时,编译器将会创建一个新类(我们将其称为 FooInt),如下所示:

struct FooInt
{
    void doSomething(int param);
}

因此,编译器需要访问方法的实现,用模板参数(本例中是int)来实例化他们。如果这些实现没有在头文件中,它们将不可访问,导致编译器无法实例化模板。

通用的解决方法是在头文件中写模板的声明,然后实现类在实现文件中(.cpp),同时在头文件尾部将实现文件include进来。

Foo.h

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

Foo.cpp

template <typename T>
void Foo<T>::doSomething(T param){
    
}

这个时候我们会遇到一个编译错误:错误 C2995 “void Foo<T>::doSomething(T)”: 函数模板已经定义

主要原因是在Foo.h头文件中包含了Foo.cpp执行文件,对成员函数进行了定义;而Foo.cpp执行文件又包含在项目中,再次对成员函数进行了定义,所以会有“函数模板已经定义”的错误信息。可以将模板类对应的Foo.cpp执行文件从项目中移除,注意是“移除”而不是“删除”。

这样的话,实现依然与声明分离,但是编译器可以正常访问它。