include guards的推荐命名规范是什么?

28
通常情况下,预编译指令的命名方式是怎样的?我经常看到这样的写法:
#ifndef FOO_H
#define FOO_H

// ...

#endif

然而,我认为这并不直观。如果没有看到文件名,很难知道FOO_H的用途和它的名称所指代的内容。
什么被认为是最佳实践?

2
虽然名称可能更或少直观,但事实是,有了一点经验,您就不再阅读这些行。眼睛和大脑习惯于 #ifdef blahblah...,我几乎从来没有真正阅读正在检查的内容,它只是一个包含保护。 - David Rodríguez - dribeas
3
关于这个问题,有一个比较有用的观点:https://dev59.com/VUrSa4cB1Zd3GeqPVlgu#1744302 - Fred Nurk
任何从事C++开发的人最好能够迅速识别头文件保护。它将始终遵循您所看到的标准。最佳实践(用引号括起来,因为它是必需的)是首先放置ifndef,紧接着是define,并在文件末尾完成。我建议您尽快学会识别这一点。 - Edward Strange
9个回答

29

我个人遵循Boost的建议。它可能是最大的高质量C++库集合之一,而且它们没有问题。

建议如下:

<project>_<path_part1>_..._<path_partN>_<file>_<extension>_INCLUDED

// include/pet/project/file.hpp
#ifndef PET_PROJECT_FILE_HPP_INCLUDED

选择的宏名称应该:

  • 合法(注意以_[A-Z]开头或包含__的都不合法)
  • 易于生成
  • 在项目内(否则会有两个文件在同一个位置)作为包含保护符号时保证唯一性
  • 保证不用于其他任何目的(如果您使用INCLUDED结束另一个宏,那么可能会产生冲突)

我已经了解GUID,但那些看起来很奇怪。

显然,我希望所有编译器都实现#pragma once(或者更好的是,#pragma multiple并且"once"是默认行为...)


个人而言,我觉得添加扩展名是多余的,但我喜欢_INCLUDED实际上表达了它的含义,因此给它加1分。我的偏好是使用INCLUDE_GUARD_FOO(没有冗余的_H,毕竟我们只需要为头文件添加包含保护),这甚至更加直接,但这只是品味问题。 - cmaster - reinstate monica
2
@cmaster 在某些情况下,添加 _H 可以帮助你同时拥有 C 和 C++ 实现某些功能的能力(分别使用 .h 和 .hpp 文件)。 - Squirrel

24

根据我的经验,惯例是使用包含它们的头文件的名称来命名包含保护措施,但名称全部大写且句点用下划线代替。

因此,test.h 变成了 TEST_H

这种惯例在现实生活中的示例包括 Qt Creator,在自动生成类头文件时遵循此约定。


5
最好只使用FILENAME_H作为包含保护名称,因为您将所有项目和所有库的所有文件都保存在同一个目录中,没有子目录,所以您知道它们永远不会具有冲突的文件名... - Fred Nurk
2
虽然这是常见的做法,但根据你的商店在#define和其他名称方面的其他用途,这可能还不够好。 - John Dibling

17

以下内容摘自Google代码风格指南

所有头文件都应有 #define guards,以防止多次包含。符号名称应遵循 <PROJECT>_<PATH>_<FILE>_H_ 的格式。为确保唯一性,该格式应基于项目源树中的完整路径。例如,在名为 foo 的项目中,位于 foo/src/bar/baz.h 的文件应具有以下保护:

 #ifndef FOO_BAR_BAZ_H_
 #define FOO_BAR_BAZ_H_
 ...
 #endif  // FOO_BAR_BAZ_H_
我在自己的项目中使用这种样式。

5
虽然一般来说谷歌的编码标准是我见过最糟糕的之一,但是我确实使用了命名空间前缀。如果在多个命名空间中存在相同名称的内容,这是绝对必要的。 - Edward Strange
我一直在尝试弄清楚,拖尾下划线背后是否有任何原因? - Toby
2
@Toby 只是为了使它更独特...如果有人已经有一个 CONFIG_H(例如包含的库),那么使用 CONFIG_H_ 就不会与其冲突。同样的原因,一些人使用前导下划线,但他们不应该这样做,因为那是保留的。 - RastaJedi
3
链接失效了,现在已更新为https://google.github.io/styleguide/cppguide.html。 - SedriX
我相信这是一种更清晰的保护头文件的方式,因为在 #endif 指令之后使用注释作为头文件名称可以将其与其他常量区分开来。 - Kassi

5
请看包含您头文件的代码。 如果它类似于:
#include "mylib/myheader.h"

mylib/myheader.h已经是一个独特的名称。只需将 / 和 . 替换为 _ 并大写即可。

#define MYLIB_MYHEADER_H

如果你的包含路径中有两个相对于包含路径具有相同名称的头文件,那么在该级别上就已经发生了冲突。

4

FOO_H替换为FOO_H_INCLUDED,这样更清晰易懂。


2

正如其他人之前提到的,一个非常常见的约定是使用大写字母版本的名称,并将点替换为下划线:foo.h -> FOO_H。

然而,这可能会导致与简单和/或常见名称发生名称冲突。因此,在非空的Visual C++项目中,自动生成的头文件(例如stdafx.h)会附加一些随机字符串,例如:

#ifndef FOO_H__NsknZfLkajnTFBpHIhKS
#define FOO_H__NsknZfLkajnTFBpHIhKS
#endif

http://www.random.org/strings/ 是一个对于此类场景非常有用的随机生成器。

另外,如果文件是某个子模块的一部分,或者其内容驻留在一个特定的命名空间中,我也会将其添加到防护范围内:

#ifndef SOMECOMPONENT_FOO_H__NsknZfLkajnTFBpHIhKS
#define SOMECOMPONENT_FOO_H__NsknZfLkajnTFBpHIhKS

namespace somecomponent
{
  ...
}

#endif

7
这些是保留名称,因为它们包含双下划线。因此,不建议使用它们作为变量名。 - Ben Voigt

2

我通常会查看现在的时间,并将其附加到字符串末尾,例如FOO_H_248。这是一种额外的预防措施,你不需要记住它,所以不必担心它看起来很难懂。


你使用12小时制还是24小时制?这是当地时间吗?;-) - T33C
8
@T33C:12?24?你们在谈什么?这只是一个普通的17小时时钟。而且它显示的是教皇当时所在地的当地时间。 - Benjamin Lindley

1

我通常使用类似于FOO_H_INCLUDED_的东西。一些(Microsoft)头文件具有非常类似GUID字符串表示形式的内容,但我从未需要过如此复杂的东西。


1
通常人们会通过文件名来实现这一点,以便每个文件的代码只被编译和添加一次。你可以将 FOO_H 命名为任何你想要的名称,但我所编写或看到的几乎所有代码都使用了文件名。只需确保它是唯一的,因为你不希望你的 FOO_H 与别人的 FOO_H 冲突。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接