事实上,在 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.
哇!
在“现实生活”中,它通常不那么复杂。大多数代码只有一个简单的标头/源代码组织,源代码中有一些内联代码。
但在其他情况下(模板对象相互了解),我必须为每个对象拥有单独的声明和实现标头,并使用包含这些标头的空源来帮助我查看一些编译错误。
将标头分解为单独标头的另一个原因可能是加快编译速度,将解析的符号数量限制在严格必要的范围内,并避免在实现内联方法时对只关心前向声明的源进行不必要的重新编译改变了。
结论
您应该使您的代码组织尽可能简单,并尽可能模块化。尽可能多地放在源文件中。仅在标头中公开需要共享的内容。
但是,如果您的代码组织变得比普通的标头/源代码组织更“有趣”...
^_^