在实现(.cpp)文件中不包括相应的头文件(.h)仍然编译?

Not including the corresponding header (.h) file in the implementation (.cpp) file still compiles?

我今天写了一个简单的例子,只是想看看它是否可以编译,当我发现它可以编译时,我真的很惊讶!

示例如下:

hello.h

#ifndef HELLO_H
#define HELLO_H

// Function prototype
void say_hello();

#endif

hello.cpp

NOTE: This does NOT include "hello.h" like it would in every C++ example I have ever seen in the history of forever!

// #include "hello.h" <-- Commented out. The corresponding header is NOT included.

#include <iostream>

void say_hello() {
    std::cout << "Hello!" << std::endl;
}

main.cpp

#include "hello.h"

int main() {
    say_hello();
}

然后我把"hello.cpp"编译成静态库如下:

g++ -c hello.cpp
ar -rvs libhello.a hello.o

然后我编译了 "main" 应用程序并将其 link 编辑到库中

g++ -o main main.cpp -L. -lhello

而且 运行 它执行得很好!

./main

Hello!


虽然我很惊讶......但我明白为什么会这样。这是因为 "hello.cpp" 中的函数没有声明为静态的,所以它具有外部 linkage 并且可以从外部看到。将其设置为静态将导致 link 由于未定义的引用而失败。

所以问题来了...如果这确实有效,那么为什么每个地方的每个人都总是在“.cpp”实现文件中包含“.h”头文件和函数声明。很明显,如果它只是定义自由函数,这是没有必要的,如果不包含头文件,一切都可以正常工作。

那么为什么我们总是包含它呢? -- 仅仅是对 linker 的工作原理普遍缺乏了解吗?或者还有更多?

如果您有 class,您需要将其包括在内以获得 class 及其成员的声明,以便它们的定义相匹配。否则,您将无法将定义和声明分开。

/// C.h
class C
{
public:
    C();
private:
    int _i;
};
/// C.cpp
// #include "C.h"

C::C() : _i(42) {} // error: 'C' does not name a type

看到它在 Coliru.

上失败了

同样,如果您有 class 模板或函数模板,它通常需要在 header 中,以便以后可以淘汰它的版本。

让我们改变您的 hello.cpp:

// #include "hello.h" <-- Commented out. The corresponding header is NOT included.

#include <iostream>

int say_hello() {
    std::cout << "Hello!" << std::endl;
    return 0;
}

这将像以前的版本一样编译。它也可能 link - 但这是不对的。 return 类型错误。

这是未定义的行为,但在许多常见的实现中,您会逃避它,因为您不使用 return 值,并且它通常在寄存器中 returned。但是,它不一定是 - 您可能会在 运行 时遇到非常奇怪的错误。特别是如果差异有点复杂(比如 returning double 当调用者期望 int - 通常会在不同的寄存器中 returned)。

如果另一方面,您写了:

#include "hello.h"

#include <iostream>

int say_hello() {
    std::cout << "Hello!" << std::endl;
    return 0;
}

然后头文件中的声明将与 CPP 文件中的定义不匹配 - 您将得到一个很好的、易于理解的编译器错误消息。

事实上,这是个好主意,如果您没有声明外部函数,GCC 会抱怨。 (如果你的命令行上有 -wall -werror,它会停止你的构建。)