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

How come you can split non template classes in a .h interface and .cpp implementation?

我了解到,如果您尝试在 .h 接口和 .cpp 实现中拆分模板化 class,则会出现链接器错误。流行的post中提到的原因是"If the implementations were not in the header, they wouldn't be accessible, and therefore the compiler wouldn't be able to instantiate the template."

我不明白的是,如果 .cpp 文件中的实现在模板化 classes 的情况下不可访问,是什么让它们可用于非模板化或常规 classes。为什么我们能够在 .h 和 .cpp 文件上拆分普通 classes 的接口和实现而不会出现链接器错误?

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);
}

此代码给出链接器错误。非模板化 classes 的类似拆分不会导致链接器错误。我可以 post 代码,但你明白了。

当你有一个模板定义时,编译单元不会添加任何东西,因为模板参数可以是很多东西,所以你无法从编译时知道要创建什么 class,如this 回答。

对于非模板化的情况,你知道你的 class 里面有什么,你不必等待给出模板参数来真正生成实际的 class,因此链接器可以看到它们(因为它们被编译成二进制文件,正如文档的 post 所说)。

如果是普通函数,编译器会直接生成代码并将生成的代码添加到编译单元。

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 参数将是什么类型。您必须实例化模板以获取其二进制形式,并且只能将其添加到生成的二进制文件中。

基本上,这又回到了旧的 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 文件包含按词法引入程序的程序文本,之前 真正的最终解析和编译发生了。

如果您确实希望在单独的 C++ 文件中实现模板函数或 class,您可以针对特定类型 explicitly instantiate。例如,在您的特定示例中,如果您将此行添加到 test.cpp,您的代码将成功 link

template int ProcessVal<int>(int val);