将您的代码分成多个文件时,究竟应该将哪些内容放入 .h 文件中,哪些内容应放入 .cpp 文件中?
12 回答
头文件 ( .h
) 旨在提供多个文件所需的信息。诸如类声明、函数原型和枚举之类的东西通常放在头文件中。一句话,“定义”。
代码文件 ( .cpp
) 旨在提供只需要在一个文件中知道的实现信息。通常,其他模块应该/永远不会访问的函数体和内部变量属于.cpp
文件。一句话,“实施”。
问自己确定什么属于哪里的最简单的问题是“如果我更改它,我是否必须更改其他文件中的代码才能使东西再次编译?” 如果答案是“是”,它可能属于头文件;如果答案是“否”,它可能属于代码文件。
事实上,在 C++ 中,这比 C 头文件/源代码组织要复杂一些。
编译器看到了什么?
编译器会看到一个大的源 (.cpp) 文件,其中包含正确的标头。源文件是将编译成目标文件的编译单元。
那么,为什么需要标头?
因为一个编译单元可能需要有关另一个编译单元中的实现的信息。因此,例如可以在一个源中编写一个函数的实现,并在另一个需要使用它的源中编写该函数的声明。
在这种情况下,有两个相同信息的副本。哪个是邪恶的...
解决方案是分享一些细节。虽然实现应该保留在 Source 中,但共享符号的声明(如函数)或结构、类、枚举等的定义可能需要共享。
标头用于放置那些共享的详细信息。
将需要在多个源之间共享的声明移至标题
而已?
在 C++ 中,还有一些其他的东西可以放在标题中,因为它们也需要共享:
- 内联代码
- 模板
- 常量(通常是你想在开关内部使用的那些......)
移至标题所有需要共享的内容,包括共享的实现
那么这是否意味着标题内可能有来源?
是的。事实上,有很多不同的东西可以在一个“标题”中(即在源之间共享)。
- 前向声明
- 函数/结构/类/模板的声明/定义
- 内联和模板化代码的实现
它变得复杂,在某些情况下(符号之间的循环依赖关系),不可能将其保留在一个标题中。
标题可以分为三个部分
这意味着,在极端情况下,您可以:
- 前向声明头
- 声明/定义标头
- 一个实现头
- 一个实现源
假设我们有一个模板化的 MyObject。我们可以有:
// - - - - MyObject_forward.hpp - - - -
// This header is included by the code which need to know MyObject
// does exist, but nothing more.
template<typename T>
class MyObject ;
.
// - - - - MyObject_declaration.hpp - - - -
// This header is included by the code which need to know how
// MyObject is defined, but nothing more.
#include <MyObject_forward.hpp>
template<typename T>
class MyObject
{
public :
MyObject() ;
// Etc.
} ;
void doSomething() ;
.
// - - - - MyObject_implementation.hpp - - - -
// This header is included by the code which need to see
// the implementation of the methods/functions of MyObject,
// but nothing more.
#include <MyObject_declaration.hpp>
template<typename T>
MyObject<T>::MyObject()
{
doSomething() ;
}
// etc.
.
// - - - - MyObject_source.cpp - - - -
// This source will have implementation that does not need to
// be shared, which, for templated code, usually means nothing...
#include <MyObject_implementation.hpp>
void doSomething()
{
// etc.
} ;
// etc.
哇!
在“现实生活”中,它通常不那么复杂。大多数代码只有一个简单的标题/源代码组织,源代码中有一些内联代码。
但在其他情况下(模板对象相互了解),我必须为每个对象提供单独的声明和实现标头,并使用包含这些标头的空源来帮助我查看一些编译错误。
将标头分解为单独标头的另一个原因可能是加快编译速度,将解析的符号数量限制在严格必要的范围内,并避免在内联方法实现发生更改时对只关心前向声明的源进行不必要的重新编译。
结论
您应该使您的代码组织尽可能简单,并尽可能模块化。尽可能多地放在源文件中。仅在标头中公开需要共享的内容。
但是,如果您的代码组织变得比普通的标头/源代码组织更“有趣”,那么您将在模板化对象之间建立循环依赖关系的那一天,不要感到惊讶......
^_^
除了所有其他答案,我将告诉您不要在头文件中放置的内容:
using
声明(最常见的是using namespace std;
)不应出现在头文件中,因为它们会污染包含它的源文件的名称空间.
什么编译成空(零二进制占用)进入头文件。
变量不会编译为空,但类型声明可以(因为它们只描述变量的行为方式)。
函数不会,但内联函数会(或宏),因为它们仅在调用时生成代码。
模板不是代码,它们只是创建代码的秘诀。所以他们也进入 h 文件。
通常,您将声明放在头文件中,将定义放在实现 (.cpp) 文件中。例外情况是模板,其中定义也必须放在标题中。
这个问题和类似的问题在 SO 上经常被问到 - 请参阅Why have header files and .cpp files in C++? 和C++ 头文件,例如代码分离。
标头 (.h)
- 接口所需的宏和包含(尽可能少)
- 函数和类的声明
- 接口文档
- 内联函数/方法的声明,如果有的话
- 全局变量的外部(如果有)
正文 (.cpp)
- 其余的宏和包括
- 包含模块的头部
- 函数和方法的定义
- 全局变量(如果有)
根据经验,您将模块的“共享”部分放在 .h 上(其他模块需要能够看到的部分),将“未共享”部分放在 .cpp 上
PD:是的,我已经包含了全局变量。我已经使用过它们一些次了,重要的是不要在标题中定义它们,否则你会得到很多模块,每个模块都定义了自己的变量。
主要是头文件包含类骨架或声明(不经常更改)
并且 cpp 文件包含类实现(经常更改)。
您的类和函数声明以及文档以及内联函数/方法的定义(尽管有些人更喜欢将它们放在单独的 .inl 文件中)。
头文件 (.h) 应该用于声明类、结构及其方法、原型等。这些对象的实现在 cpp 中进行。
在.h
class Foo {
int j;
Foo();
Foo(int)
void DoSomething();
}
标头定义了一些东西,但没有说明任何关于实现的信息。(不包括此“元前”中的模板。
话虽如此,您需要将“定义”划分为子组,在这种情况下,有两种类型的定义。
- 你定义你的结构的“布局”,只告诉周围使用组需要的东西。
- 变量、函数和类的定义。
现在,我当然是在谈论第一个小组。
标题用于定义结构的布局,以帮助软件的其余部分使用实现。您可能希望将其视为您的实现的“抽象”,这是粗俗的说法,但我认为它非常适合这种情况。
正如之前的海报所说并显示您声明私有和公共使用区域及其标题,这还包括私有和公共变量。现在,我不想在此处进行代码设计,但是,您可能需要考虑在标题中放置的内容,因为那是最终用户和实现之间的层。
- 头文件 - 在开发过程中不应该经常更改 -> 你应该思考并立即编写它们(在理想情况下)
- 源文件 - 实施期间的更改
我希望看到:
- 声明
- 注释
- 标记为内联的定义
- 模板
真正的答案是不要放入:
- 定义(可能导致事物被多重定义)
- 使用声明/指令(将它们强加于包括您的标头在内的任何人,可能导致名称冲突)