我知道在头文件中提供定义允许其他文件引用它们,然后链接器将所有目标文件添加在一起并稍后提供定义。
这只是为了让我们可以在其他库的其他地方重用实现吗?
如果我们不使用头文件或滥用头文件并将所有代码放入其中,即完整的实现会发生什么?编译每个文件是否需要更长的时间,因为需要在对等文件的基础上编译完整的定义?是否会导致链接器出现问题,因为这将是同一实现的多个编译版本?
这如何与模板一起使用?
布莱尔
每次新的编译目标包含该头文件时,都会编译放置在头文件中的任何代码,因此至少是的,您将需要更长的时间来编译。不过,这通常不是重大问题。
假设你有这个标题:
int f_header() {
return 0;
}
还有这些源文件:
/* a.cpp */
#include "myheader.h"
int f_a() {
return f_header();
}
/* b.cpp */
#include "myheader.h"
int f_b() {
return f_header();
}
现在,当您编译每个源代码时,编译将成功。但是,当您链接程序时,链接器将失败,因为您将提供该函数f_header()
两次。
模板是一种特殊情况,因为它们实际上不是代码,而是为要根据不同类型生成的代码提供模板。尽管当多个源文件以相同类型使用它们时会出现重复,但编译器能够为您处理这种特殊情况。
拆分源文件和头文件中的代码(是?)在历史上或多或少是需要的。它允许编译器单独编译大型项目的代码单元,而整个项目太大而无法一次编译。
并且它允许缓存未更改的编译单元,从而进一步缩短了编译时间。
所以从技术上讲,编译器会看到一整套不同的编译单元。如果您提供相同定义的多个实现,编译器将无法告诉您。但是一旦链接器将已编译的单元打包到一个可执行文件中,它就会绊倒它。
今天,像 C# 或 Java 这样的“现代”语言已经摆脱了这种历史限制。但是 C++ 是一门复杂的语言,即使对于编译器来说也是如此,所以这些优势在某种程度上仍然是相关的。
编译器一次编译一个文件。编译器正在解析的源文件可能包含其他文件,但所有这些包含的文件在预编译阶段都“扁平化”为单一源,编译器“看到”一个单一的代码实体。
由于上述原因,代码是在 .cpp 文件中还是在 .h 文件中对编译器来说并不重要,因为编译器不会做出这种区分。但是,如果某些函数定义或某些变量定义在 .h 文件中,则问题会出现在链接阶段。
链接器看不到源文件,但可以看到目标文件 (.obj),这是编译器生成的结果。链接器知道每个目标文件中的每个函数和每个变量,链接器的工作是将它们全部绑定(或链接)到一个可执行文件中。如果在不同的目标文件中存在多个同名的变量或函数,链接器将不知道如何处理它们,并且会报错。可以通过选项强制链接器忽略所有额外的变量和函数,但不要轻易使用该选项。同名的两个函数实际上可能完全不同,忽略其中一个会导致各种错误。
另一方面,模板根本不是函数。它们是实际功能的蓝图。当编译器遇到模板时,不会发生任何实质性的事情。只有当模板被实例化为实际函数时,编译器才会生成这样的函数。因为这个模板可以驻留在头文件中就好了。