在实现(.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,它会停止你的构建。)
我今天写了一个简单的例子,只是想看看它是否可以编译,当我发现它可以编译时,我真的很惊讶!
示例如下:
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,它会停止你的构建。)