我理解如果您尝试在.h接口和.cpp实现中拆分模板化类,则会出现链接器错误。在一篇热门帖子中提到的原因是“如果实现不在标题中,则它们将无法访问,因此编译器将无法实例化模板。”
我不理解的是,如果.cpp文件中的实现在模板化类的情况下是不可访问的,那么是什么使得它们可以被非模板化或只是常规类访问。为什么我们能够在.h和.cpp文件中拆分普通类的接口和实现,而不会出现链接器错误?
Test.h
template<typename TypeOne>
TypeOne ProcessVal(TypeOne val);
Test.cpp的
template<typename TypeOne>
TypeOne ProcessVal(TypeOne val)
{
// Process it here.
return val;
}
Main.cpp的
void main()
{
int a, b;
b = ProcessVal(a);
}
此代码提供链接器错误。类似的非模板化类的拆分不会给Linker带来错误。我可以发布代码,但你明白了。
答案 0 :(得分:4)
如果是普通函数,编译器会直接生成代码并将生成的代码添加到编译单元。
<强> Test.cpp的强>
int ProcessVal(int val)
{
// Process it here.
return val;
}
如果是上述代码,则所有必要信息都是已知的,函数ProcessVal
的C ++代码可以转换为机器指令。因此,目标文件(可能称为Test.o
)将包含ProcessVal
符号+相应的代码,链接器可以引用它(生成调用或执行内联)。
另一方面,这段代码:
<强> Test.cpp的强>
template<typename TypeOne>
TypeOne ProcessVal(TypeOne val)
{
// Process it here.
return val;
}
不向编译单元提供任何输出。此编译单元(Test.o
)的目标文件将不包含ProcessVal()
函数的任何代码,因为编译器不知道TypeOne
参数将是什么类型。您必须实例化模板以获取其二进制形式,并且只能将其添加到生成的二进制文件中。
答案 1 :(得分:1)
当你有一个模板定义时,没有任何东西被添加到编译单元,因为模板参数可能很多,所以你无法从编译时知道要创建什么类,如this中所述
使用非模板化的情况,你知道你的类中有什么,你不必等待给出一个模板参数,以便真正生成实际的类,因此链接器可以看到它们(因为它们被编译成二进制文件,如文档的帖子所示。)
答案 2 :(得分:0)
基本上,这可以追溯到旧的C语言。 .h
文件旨在使用C预处理器完成,并且预处理器实际上将文本文本包含在C源代码中。所以如果你有foo.h
:
int i = 0;
和foo.c
:
#include "foo.h"
int main(){ printf("%d\n", i);}
当预处理器完成后,编译器实际上看到了:
int i = 0;
int main(){ printf("%d\n", i);}
作为源文件。没有机会出现链接器错误,因为链接器从未涉及 - 您只编译了一个C文件。
虽然模板的语义现在稍微复杂一些,但编程模型仍然相同:您的.h
文件包含以词汇方式引入程序的程序文本,之前实际最终解析和编译发生。
答案 3 :(得分:0)
如果您确实希望在单独的C ++文件中实现模板函数或类,则可以将explicitly instantiate用于某种类型。例如,在您的特定示例中,如果您将此行添加到test.cpp
,您的代码将成功链接
template int ProcessVal<int>(int val);