C++头文件问题

3
我在使用类时尝试了一些C++代码,这个问题困扰着我。我已经创建了一个包含类定义的头文件和一个包含实现的cpp文件。如果我在另一个cpp文件中使用这个类,为什么要包含头文件而不是包含包含类实现的cpp文件呢?如果我包含类实现文件,那么类头文件应该自动导入(因为我已经在实现文件中包含了头文件),这不更自然吗?很抱歉如果这是一个愚蠢的问题,我真的很想知道为什么大多数人包含.h而不是.cpp文件,后者似乎更自然(我至少知道Python)。这是历史原因还是与程序组织有关的技术原因,或者其他原因?

1
几乎总是错误的包含 cpp 文件。 - fredoverflow
4
如果你正在学习这门语言,那么问这个问题并不愚蠢! - lornova
Jimbo:你的问题让我想起了我最近给出的这个答案……我不知道它是否对你有用。它还涉及到Python和C++编译模型之间的差异。 - David Z
@Lorenzo,SO上有很多关于头文件的问题(我阅读了几个与此最接近的问题,但没有得到令人满意的答案),而且上述做法(即包含.h文件)似乎在c++/qt示例代码中已经非常深入人心,如果我问一些非常明显的问题,可能会引起一些不满。 - jimbo
@David,我正在查看链接,谢谢。 - jimbo
3个回答

14
因为当你编译另一个文件时,C++ 实际上并不需要知道实现细节。它只需要知道每个函数的签名(它接受哪些参数和返回什么),每个类的名称,哪些宏被 #define 定义,以及其他类似的“摘要”信息,以便它可以检查你是否正确地使用函数和类。不同的 .cpp 文件的内容直到链接器运行时才会放在一起。
例如,假设你有 foo.h 文件。
int foo(int a, float b);

foo.cpp

#include "foo.h"
int foo(int a, float b) { /* implementation */ }

以及bar.cpp

#include "foo.h"
int bar(void) {
    int c = foo(1, 2.1);
}
当你编译foo.cpp时,它会变成foo.o,编译bar.cpp也是类似的生成bar.o。在编译过程中,编译器需要检查foo.cpp中函数foo()的定义,确保其与bar.cpp中对foo()的使用一致(即以intfloat作为参数并返回一个int)。它通过在两个.cpp文件中都包含相同的头文件来实现这一点。如果定义和使用都符合头文件中的声明,则它们必须相互一致。
但是,编译器实际上并不会将foo()的实现包含在bar.o中。它只会包含一个汇编语言指令去调用foo。因此,在创建bar.o时,它不需要了解foo.cpp的内容。但是,在链接阶段(即在编译之后),连接器确实需要知道foo()的实现,因为它将包含该实现到最终程序中,并将call foo指令替换为call 0x109d9829(或者连接器决定的函数foo()在内存中的地址)。
请注意,链接器不检查foo()的实现(在foo.o中)与对foo()的使用(在bar.o中)是否一致——例如,它不检查是否以正确的intfloat参数调用了foo()!这种检查在汇编语言中有点困难(至少比在C++源代码中检查要困难),因此连接器依赖于已知编译器已经进行过检查。这就是为什么需要头文件来向编译器提供这些信息的原因。

那解决了所有问题,非常感谢您提供详尽和详细的答案。 - jimbo

1

这个魔法是由链接器完成的。每个 .cpp 文件在编译时都会生成一个带有所有导出和导入符号的中间对象文件表。链接器将对它们进行调和。换句话说,您只需包含头文件,每次引用所包含的类时,编译器将把所引用类的签名放入符号表中。

如果您包含 .cpp 文件,则会将相同的代码编译两次,并且会出现链接错误,因为链接器会找到相同的符号,从而产生歧义。


0
一个技术原因是编译速度。假设你的类使用了其他10个类(例如作为成员变量类型)。包括所有10个类的长.cpp文件会使得你的类编译速度变慢(即可能需要2秒而不是1秒)。
另一个原因是隐藏实现。假设你正在编写一个供公司内其他10个团队使用的类。他们只需要知道和学习.h文件(公共接口)中关于你的类的内容。你可以在.cpp文件(实现)中自由地进行任何操作,可以随意更改,他们不会在意。但如果你更改了.h文件,他们可能需要调整使用你的类的代码。
对于每个方法体,你可以选择将其放入.h文件或.cpp文件中。如果它在.h文件中,当被调用时编译器可以将其内联,这可能会使代码稍微快一些。但编译速度会变慢,临时.o(.obj)文件可能会变大(因为每个文件都包含已编译的方法体),程序二进制文件(.exe)可能会变大,因为函数体被内联的次数越多,占用的空间就越大。

真正的原因不是速度,而是它甚至无法链接!你会收到一个“符号已在对象中定义”的错误! - lornova
@Lorenzo:我认为没有“真正的原因”。你说得对,大多数情况下,#include一个.cpp文件会导致链接器错误。我的观点是,如果C++编译是按照必须#包含.cpp文件的方式设计的,整个项目的编译速度会慢得多。 - pts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接