什么绝对不应该包含在头文件中?

例如,如果我正在使用具有很多常量的已记录的行业标准格式,那么这样做是一种好习惯吗?在头文件中定义它们(如果我正在为该格式编写解析器)?

头文件应包含哪些函数?
哪些功能不应该?

评论

简短而轻松:多个模块中需要的定义和声明。

将这个问题标记为“过于广泛”和结束是绝对适度的中庸之道。这个问题恰好问我在寻找什么-这个问题的格式正确,并且提出了一个非常明确的问题:最佳实践是什么?如果对于软件工程来说“太宽泛”了,那么我们也可以关闭整个论坛。

TL;博士对于C ++,在Bjarne Stroustrup(其创建者)编写的第四版“ C ++编程语言”中,在15.2.2节中描述了标题应该包含和不应包含的内容。我知道您将问题标记为C,但其中一些建议也适用。我认为这是一个很好的问题...

#1 楼

标头内容:


当源文件中包含标头时,使标头可编译所需的最小#include指令集。
预处理器符号定义需要共享并且只能通过预处理器完成的事情。即使在C语言中,预处理器符号也最好保持最小。
可进行结构体声明的前向声明,以使标头主体中的结构定义,函数原型和全局变量声明可编译。
在多个源文件之间共享的数据结构和枚举的定义。
声明链接程序可以看到其定义的函数和变量。
内联函数定义,但请注意此处。

标头中不包含什么:


免费的#include指令。这些无用的内容会导致不需要重新编译的内容重新编译,并且有时可以重新编译,从而导致系统无法编译。如果头文件本身不需要其他头文件,请不要在头文件中请求文件。
预处理器符号的意图可以通过某种机制来实现,而不是通过预处理器。以及许多结构定义。将它们分成单独的标头。
需要附加#include的函数的内联定义,可能更改或太大的函数。那些内联函数应该几乎没有扇出,如果确实有扇出,则应该将其本地化为标头中定义的内容。 >
事实证明这是一个不平凡的问题。 TL; DR定义:头文件必须包括直接定义直接使用的每种类型或直接声明所涉及的头文件中使用的每个功能的头文件,但不得包含任何其他内容。指针或C ++引用类型不适合直接使用;最好使用正向引用。

有一个免费的#include指令的位置,这是在自动测试中。对于软件包中的每个头文件,我都会自动生成并编译以下内容:

#include "path/to/random/header_under_test"
int main () { return 0; }


编译应该干净(即没有任何警告或错误) 。关于不完整类型或未知类型的警告或错误意味着被测试的头文件缺少#include伪指令和/或缺少前向声明。请注意:仅仅通过测试并不意味着#include指令集就足够了,更不用说最小化了。

评论


因此,如果我有一个库定义了一个名为A的结构,而这个库B则使用了该结构,而库B由程序C使用,那么我应该在库B的主头文件中包含库A的头文件,还是我只是向前宣布吗?库A在编译过程中被编译并与库B链接。

– MarcusJ
18年7月11日在14:34

@MarcusJ-我在标题不属于标题下列出的第一件事是免费的#include语句。如果头文件B不取决于头文件A中的定义,则不要在头文件B中#include头文件A。头文件不是指定第三方依赖项或构建指令的地方。这些文件会放在其他地方,例如顶级自述文件。

–David Hammen
18年7月12日在3:24

@MarcusJ-我更新了我的答案,试图回答您的问题。请注意,您的问题没有一个答案。我将举例说明两个极端。情况1:库B直接使用库A的功能的唯一位置是库B源文件中。情况2:库B是库A中功能的瘦扩展,库B的头文件直接使用库A中定义的类型和/或函数。在情况1中,没有理由在在库2的标头中。在第2种情况下,这种暴露是强制性的。

–David Hammen
18年7月13日在20:55

是的,是第2种情况,对不起,我的评论忽略了它使用的是库B的标头中库A中声明的类型的事实,我想我可能可以转发声明它,但我认为这样行不通。感谢更新。

– MarcusJ
18年7月14日在22:15

将常量添加到头文件中是大禁忌吗?

–mding5692
18年11月17日在19:57

#2 楼

除了已经说过的内容。

H文件应始终包含:



源代码文档!!!至少,函数的各种参数和返回值的用途是什么。
标题防护,#ifndef MYHEADER_H #define MYHEADER_H ... #endif

H文件永远不应包含:


任何形式的数据分配。
函数定义。内联函数在某些情况下可能是一种罕见的例外。
标有static的任何内容。
与应用程序其余部分无关的Typedef,#define或常量。还会说,从来没有任何理由在任何地方使用非常量全局/外部变量,但这是另一篇文章的讨论。)

评论


除了您强调的内容,我都同意。如果要制作图书馆,是的,您应该记录文件或图书馆的用户。对于内部项目,如果您使用良好的,易于说明的变量和函数名称,则无需在文档中弄乱标题。

–martiert
2012年10月8日14:02

@martiert我也是“让代码说明一切”的学校。但是,即使您自己使用这些功能,您也至少应该始终记录它们的功能。特别令人感兴趣的事情是:如果函数具有错误处理,它将返回什么错误代码以及在什么情况下会失败?如果函数失败,参数(缓冲区,指针等)会怎样?另一个非常相关的事情是:指针参数是否向调用者返回一些东西,即它们是否期望分配的内存? ->

–user29079
2012年10月8日14:09

对于调用者应该显而易见的是,在函数内部完成了什么错误处理而没有完成什么。如果函数期望分配的缓冲区,则很有可能也会将越界检查也留给调用方。如果该函数依赖于要执行的另一个函数,则必须对此进行记录(即在link_list_add()之前运行link_list_init())。最后,如果函数具有“副作用”,例如创建文件,线程,计时器等,则应在文档中说明。 ->

–user29079
2012年10月8日14:12

也许“源代码文档”在这里太宽泛了,这确实属于源代码。具有输入和输出,前置条件和后置条件以及副作用的“使用文档”一定应该放在那里,而不是史诗般的,而是简短的。

–安全
2012年10月8日14:19

有点迟了,但是文档是+1。为什么存在此类?该代码不能说明一切。此功能有什么作用? RTFC(读取精细的.cpp文件)是淫秽的四个字母的缩写。永远不要对RTFC有所了解。标头中的原型应在一些可提取的注释(例如doxygen)中总结出什么是自变量以及函数的作用。为什么该数据成员存在,包含哪些内容以及以米,英尺或弗朗斯为单位的值?对于(可提取的)评论也存在另一个主题。

–David Hammen
13-10-14在12:55



#3 楼

头文件应具有以下组织:


类型和常量定义
外部对象声明
外部函数声明

头文件不应包含对象定义,仅包含类型定义和对象声明。

评论


内联函数定义呢?

–科斯
2012年10月6日10:00

如果内联函数是仅在一个C模块内部使用的“辅助”函数,则仅将其放入该.c文件中。如果内联函数必须对两个或多个模块可见,则将其放入头文件中。

–theD
2012年10月6日10:16



此外,如果必须在库边界上可见该函数,则不要使其内联,因为这会迫使每次使用该库的人在每次修改内容时都重新编译。

–研究员
2012年10月6日,12:11

@DonalFellows:这是一个反手的解决方案。更好的规则:不要将标头放在经常修改的标头中。如果该函数没有扇出并且具有清晰的定义(仅在基础数据结构发生更改时才更改),则在标头中内联简短的小函数没有错。如果函数定义由于基础结构定义发生变化而发生变化,是的,您必须重新编译所有内容,但是由于结构定义发生了变化,因此无论如何都必须这样做。

–David Hammen
2012年10月6日14:11



那么常量定义不是对象定义吗?就像const int x = 5;还是您在这里谈论宏?

– Mercury0114
20-10-10在19:55

#4 楼

我可能永远不会说永远不会,但是在解析时生成数据和代码的语句不应放在.h文件中。

宏,内联函数和模板可能看起来像数据或代码,但是它们不会在解析时生成代码,而是在使用时生成代码。这些项目通常需要在多个.c或.cpp中使用,因此它们属于.h。

在我看来,头文件应该具有对应于的最小实际接口。 c或.cpp。该接口可以包括#defines,class,typedef,struct定义,函数原型,以及用于全局变量的次要外部定义。但是,如果仅在一个源文件中使用声明,则可能应将其从.h中排除,而应包含在源文件中。

有些人可能会不同意,但是我个人对.h文件的标准是它们#include所有其他需要编译的.h文件。在某些情况下,这可能是很多文件,因此我们有一些有效的方法来减少外部依赖性,例如对类的前向声明,这些类使我们可以使用指向类对象的指针,而不必包括可能包含包含文件的大树。 br />

#5 楼

在解析时生成数据和代码的语句不应放在.h文件中。就我的观点而言,头文件应仅具有对应于.c.cpp的最小实际接口。