C++中头文件保护的问题

4

我是一名新手,正在学习编写自己的头文件。虽然出于必要性,但我必须学会。

我正在编写一个头文件,并尝试理解头文件保护。在包含的头文件前后有一个或两个下划线是否有区别?

以这个假设性的例子为例:x.h

//x.h
#ifndef __X_H_INCLUDED__
#define __X_H_INCLUDED__
//functions n stuff
#endif

对比:

//x.h
#ifndef _X_H_INCLUDED_
#define _X_H_INCLUDED_
//functions n stuff
#endif

另一种方式比另一种方式更正确吗?它们之间有什么区别吗?

4
双下划线被保留给实现 - 在你自己的头文件中使用它们是未定义行为,通常不是一个好主意。 - Chris Dodd
还要注意 #pragma once,请参考 https://dev59.com/gXM_5IYBdhLWcg3w-4dg - Anthony
2
似乎没有前导下划线会更好,为什么不只是 X_H_INCLUDED - Tas
1
@Anthony,按照定义,编译指示是非可移植的。当然,如果您只想使用支持它的编译器(我认为可能有很多:https://en.wikipedia.org/wiki/Pragma_once#Portability),那么这不是问题。 - paxdiablo
根据链接的问题,有相当多。我通常同时使用#pragma once和包含保护。 - Anthony
3个回答

8
根据C++11 17.6.4.3.2全局名称(尽管这个限制已经存在一段时间):
某些名称集和函数签名始终保留给实现:
- 每个名称包含双下划线__或以下划线后跟大写字母开头的名称都保留给实现任意使用。 - 每个以下划线开头的名称保留给实现在全局命名空间中使用的名称。
因此,如果想使软件可移植,你确实不应该使用任何一种方式。尽管如此,很多人仍然使用这两种变体。
如果您想更加“安全”,可以使用类似于GUARD_X_H或X_H_INCLUDED的东西,当然,您仍然需要注意冲突。您可能采用Java的方式,最终得到类似以下宏的结果:
AU_COM_POWERFIELD_DATASTRUCTURES_TREES_BALANCED_BTREE_H

只要你在宏名称的实现限制范围内(从记忆中至少为1024个字符),就可以满足要求。
或者,如果您想牺牲可移植性(但不是 很多 可移植性,因为许多编译器都支持此功能),您可以尝试使用 #pragma once,这样您就不必担心想出唯一的名称。

2

来自2003年的C++标准(这些规则仍然适用):

17.4.3.2.1 全局名称[lib.global.names]

某些名称和函数签名始终保留给实现:

每个名称,其中包含双下划线(__)或以下划线开头,后跟大写字母(2.11),都保留给实现以供任何用途。 每个以下划线开头的名称都被保留给实现,作为全局命名空间中的名称。165 165)这些名称也在命名空间::std(17.4.3.1)中保留。


1

我喜欢遵循特定的包含保护结构,以匹配项目的结构。

#ifndef PROJECT_PATH_TO_FILE_HPP
// etc. etc.

如果项目名称为"skittles",头文件的路径为taste/the/rainbow.hpp,那么我的包含保护变成:

#ifndef SKITTLES_TASTE_THE_RAINBOW_HPP
// etc. etc.

这个方法非常有效,但要注意文件名和目录名之间的名称冲突。例如,在项目根目录中有一个名为foo_bar.hpp的文件,还有一个在foo/bar.hpp路径下的文件。这将导致名称冲突,需要解决。
另一种选择是使用命名空间而不是路径:
#define PROJECT_NAMESPACE_MODULE_HPP

我还见过人们在文件名后添加文件创建的日期和时间,或使用UUID来进一步防止名称冲突。

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