为什么你可以在.h接口和.cpp实现中拆分非模板类?

时间:2015-01-04 01:40:33

标签: c++ templates

我理解如果您尝试在.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带来错误。我可以发布代码,但你明白了。

4 个答案:

答案 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);