在C++标识符中使用下划线的规则是什么?

1055
在C++中,通常会给成员变量命名一种前缀,以表示它们是成员变量,而不是局部变量或参数。如果你来自MFC背景,你可能会使用m_foo。我也偶尔见过myFoo。
C#(或可能只是.NET)似乎建议使用下划线,比如_foo。这在C++标准中允许吗?

3
有关此事的glibc手册页面可以在http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html找到。编辑:另请参阅http://www.opengroup.org/onlinepubs/009695399/functions/xsh\_chap02\_02.html。 - CesarB
10
请注意,不遵守这些规则并不一定意味着您的代码无法编译或运行,但很可能您的代码无法在不同的编译器和版本之间移植,因为无法保证不会出现名称冲突。为了支持这一点,我知道某个重要系统的某些实现一直使用 _ 大写字母作为命名约定。尽管没有由此产生错误,但这当然是不好的做法。 - g24l
6个回答

953
规则(在C++11中没有改变):
  • 在任何范围内保留,包括用作实现宏的标识符:
    • 以下划线开头,紧接着一个大写字母的标识符
    • 包含相邻下划线(或“双下划线”)的标识符
  • 在全局命名空间中保留:
    • 以下划线开头的标识符
  • 此外,std命名空间中的所有内容都是保留的。(尽管可以添加模板特化。)

来自2003年的C++标准:

17.4.3.1.2 全局名称 [lib.global.names]
某些名称和函数签名集合始终保留给实现:
- 任何包含双下划线(__)或以下划线开头后跟大写字母(2.11)的名称都保留给实现进行任何用途。 - 任何以下划线开头的名称都保留给实现在全局命名空间中使用(165)。
(165)这些名称在命名空间::std(17.4.3.1)中也是保留的。
C++语言基于C语言(1.1/2,C++03),C99是一个规范性参考(1.2/1,C++03),因此了解1999年C标准的限制是有用的(尽管它们不直接适用于C++)。
7.1.3 保留标识符
每个头文件声明或定义其关联子条款中列出的所有标识符,并可选择声明或定义其关联未来库方向子条款中列出的标识符,以及始终保留为任何用途或用作文件范围标识符的标识符。
以下划线开头且后跟大写字母或下划线的所有标识符始终保留为任何用途。
以下划线开头的所有标识符始终保留为普通和标签名称空间中的文件范围标识符。
以下任何子条款中的每个宏名称(包括未来库方向)都保留为指定用途,如果包含了其关联头文件之一,除非另有明确说明(参见7.1.4)。
以下任何子条款中具有外部链接的所有标识符(包括未来库方向)始终保留为具有外部链接的标识符。
以下任何子条款中列出的具有文件范围的每个标识符(包括未来库方向)如果包含了其关联头文件之一,将保留为宏名称和具有相同名称空间中的文件范围标识符。
没有其他标识符被保留。如果程序在保留标识符的上下文中声明或定义标识符(除非符合7.1.4的规定),或者将保留标识符定义为宏名称,行为是未定义的。
如果程序使用#undef删除了第一组中列出的任何标识符的宏定义,行为是未定义的。
154)具有外部链接的保留标识符列表包括errno、math_errhandling、setjmp和va_end。
其他限制可能适用。例如,POSIX标准保留了许多在正常代码中可能出现的标识符。
  • 以大写字母 E 开头,后跟数字或大写字母的名称:
  • 可用于额外的错误代码名称。
  • isto 开头,后跟小写字母的名称
  • 可用于额外的字符测试和转换函数。
  • LC_ 开头,后跟大写字母的名称
  • 可用于指定区域设置属性的额外宏。
  • fl 结尾的所有现有数学函数的名称
  • 保留用于相应的操作浮点数和长双精度参数的函数。
  • SIG 开头,后跟大写字母的名称
  • 保留用于额外的信号名称。
  • SIG_ 开头,后跟大写字母的名称
  • 保留用于额外的信号动作。
  • strmemwcs 开头,后跟小写字母的名称
  • 保留用于额外的字符串和数组函数。
  • PRISCN 开头,后跟任何小写字母或 X 的名称
  • 保留用于额外的格式说明符宏。
  • _t 结尾的名称
  • 保留用于额外的类型名称。
尽管目前为止在您自己的用途中使用这些名称可能不会引起问题,但它们确实可能与未来版本的标准产生冲突的可能性。
个人而言,我不会以下划线开头命名标识符。对我的规则进行了新的补充:不要在任何地方使用双下划线,这很容易,因为我很少使用下划线。
在对这篇文章进行研究后,我不再以_t结尾命名标识符,因为这是POSIX标准保留的。
关于任何以_t结尾的标识符的规定让我感到非常惊讶。我认为这是POSIX标准(还不确定)寻求澄清和官方章节的规定。这来自于GNU libtool manual,列出了保留的名称。
CesarB提供了以下链接到POSIX 2004保留的符号,并指出“许多其他保留的前缀和后缀...可以在那里找到”。POSIX 2008保留的符号在这里定义。这些限制比上述的要更加微妙。

17
C++标准没有“导入”C语言标准,对吧?据我所知,它们只是导入了某些头文件,而不是整个语言或命名规则。但是,确实像_t这样的东西也让我感到惊讶。但由于它是C语言标准,所以只能适用于全局命名空间。根据阅读结果,在类内部使用_t应该是安全的。 - jalf
28
C++标准并不会“导入”C标准,而是引用C标准。C++库介绍中提到:“该库还提供了标准C库的功能”。这是通过包含经过适当更改的C标准库头文件来实现的,而不是通过“导入”它来实现的。C++标准有一套规则,描述了保留名称。如果C中保留的名称也应该在C++中保留,那么这就是说这个地方。但C++标准没有这样说。因此,我不相信在C中保留的内容也在C++中保留,但我可能是错的。 - Johannes Schaub - litb
10
关于 "_t" 问题,我找到了这些信息:C99 TC3 的 n1256 文件中写道:"以 int 或 uint 开头、以 _t 结尾的 typedef 名称"是被保留的。我认为这仍然允许使用像 "foo_t" 这样的名称,但我认为这些名称会被 POSIX 所保留。 - Johannes Schaub - litb
70
“宽容性”一词被 POSIX 保留,因为它以“to”加小写字母开头?我敢打赌很多代码都违反了这个规则! - Sjoerd
24
“C++标准是以C标准为基础定义的。基本上,它说C++是C语言加上这些差异和补充。”这是无稽之谈!C++只在[basic.fundamental]和库中参考了C标准。如果您所说的是真的,那么C++在哪里说 _Bool 和 _Imaginary 在C++中不存在? C++语言是明确定义的,而不是根据对C进行“编辑”来定义的,否则标准可能会更短! - Jonathan Wakely
显示剩余43条评论

222

避免命名冲突的规则在C++标准(见Stroustrup书籍)和C++大师(Sutter等人)提到过。

个人规则

因为我不想处理情况,想要一个简单的规则,所以我设计了一个个人的规则,既简单又正确:

当给一个符号命名时,如果你遵循以下规则,就可以避免与编译器/操作系统/标准库发生冲突:

  • 永远不要以下划线开头命名一个符号
  • 永远不要在符号内部使用两个连续的下划线来命名。

当然,将代码放在唯一的名称空间中有助于避免冲突(但无法防止恶意宏)。

一些例子

(我使用宏,因为它们是C/C++符号中最能污染代码的,但它可以是任何东西,从变量名到类名)

#define _WRONG
#define __WRONG_AGAIN
#define RIGHT_
#define WRONG__WRONG
#define RIGHT_RIGHT
#define RIGHT_x_RIGHT

C++0x草案摘录

n3242.pdf文件中提取(我预计最终标准文本会类似):

17.6.3.3.2 全局名称 [global.names]

某些名称集和函数签名总是保留给实现:

— 每个名称,其中包含双下划线__或以下划线后跟大写字母开头(2.12),都对实现保留。

— 以下划线开头的每个名称都被保留给实现,作为全局命名空间中的名称。

但也有:

17.6.3.3.5 用户自定义字面量后缀 [usrlit.suffix]

不以下划线开头的字面量后缀标识符保留供将来标准化使用。

除非您考虑到一个以一个下划线开头并后跟小写字母的名称如果在全局命名空间中未被定义,否则这个最后一条款很令人困惑...


11
“__WRONG_AGAIN__”包含两个连续的下划线(一个在开头,一个在结尾),因此根据标准来说是错误的。 - paercebal
8
“WRONG__WRONG”包含连续的下划线(中间有两个),因此根据标准是错误的。 - paercebal
3
将您的代码放在独特的命名空间中有助于避免冲突,但这仍然不够,因为标识符可能与关键字发生冲突,无论作用域如何(例如,对于GCC的__attribute__)。 - Ruslan
1
根据标准,为什么在中间有两个连续的下划线会有问题?用户定义的字面值后缀适用于像1234567L4.0f这样的字面值;如果我没记错,这是指ohttp://en.cppreference.com/w/cpp/language/user_literal - Jason S
5
根据标准,为什么在中间连续有两个下划线会有问题?因为标准规定这些是保留字。这不是关于好坏风格的建议,而是来自标准的决定。为什么会做出这样的决定呢?我猜之前的编译器在标准化之前已经非正式地使用了这种约定。 - paercebal
显示剩余3条评论

50

来自MSDN

在所有作用域中,两个连续的下划线字符(__)开头的标识符,或者一个前导下划线后跟一个大写字母的标识符,都为C++实现保留。您应该避免使用一个前导下划线后跟一个小写字母的名称作为文件作用域的名称,因为可能会与当前或未来的保留标识符发生冲突。

这意味着您可以使用单个下划线作为成员变量前缀,只要它后面跟着一个小写字母即可。

这显然来自C++标准的17.4.3.1.2节,但我找不到在线完整标准的原始来源。

另请参见此问题


2
我在n3092.pdf(C++0x标准草案)的第17.6.3.3.2节中找到了类似的文本。 - paercebal
9
有趣的是,这似乎是唯一直接、简洁回答问题的答案。 - hyde
11
@hyde: 实际上并不是这样的,因为它跳过了全局命名空间中不允许使用下划线开头的标识符的规则。请参见Roger的回答。我会非常谨慎地引用MS VC文档作为C++标准的权威资料。 - sbi
5
首先,我仍然认为缺乏任何提示表明相同规则不适用于全局命名空间是一种失败。更糟糕的是,相邻的下划线不仅在标识符开头禁止使用,而且在任何地方都被禁止使用。因此,这个答案不仅省略了一个事实,而且实际上至少有一个错误的声明。正如我所说的,除非问题只涉及VC,否则我不会参考MSVC文档。 - sbi
1
@sbi 这其中有一些讽刺意味,VC遵循ISO C++标准,该标准也保留了使用单下划线的名称,同时重命名了一些posix函数,例如_dup()而不是dup()。 - Swift - Friday Pie
显示剩余4条评论

29

至于问题的另一部分,通常将下划线放在变量名的末尾,以免与任何内部内容冲突。

即使在类和命名空间中也这样做,因为我只需要记住一个规则(与“在全局范围内名称的结尾,在其他地方名称的开头”相比)。


2

是的,在标识符中可以使用下划线。我相信规则是:第一个字符可以使用a-z、A-Z、_中的任意一个,后面的字符可以使用+0-9。

在C代码中,下划线前缀很常见——单个下划线表示“私有”,双下划线通常保留给编译器使用。


7
它们在图书馆很常见,但不应该在用户代码中普遍存在。 - Martin York
61
你知道人们确实用C语言编写库。 - John Millikin
12
“是的,在标识符中可以使用下划线。” 这个说法对于全局标识符来说是错误的。请参考 Roger 的回答 - sbi
1
根据C和C++标准,全局标识符以下划线开头在语义上是被保留的。虽然它们在语法上是有效的标识符,编译器不会阻止您将函数命名为“_Foo”,但这样做依赖于非标准实现细节,因此可能会冒着代码在未来版本的语言/标准库实现/操作系统中出错的风险。 - BenW
据我所知,C++标准仅规定不允许以下划线开头的全局标识符,而没有对语法和语义进行任何区分。(还包括以下划线后跟大写字母的标识符以及具有两个连续下划线的标识符。) - sbi

0
首先,当前工作草案中的规则在[lex.name] p3中列出:
此外,一些作为“标记”或“预处理标记”的标识符是为C++实现保留的,不应以其他方式使用;不需要进行诊断。
- 每个包含双下划线“__”或以下划线后跟大写字母开头的标识符都保留给实现用于任何目的。 - 每个以下划线开头的标识符都保留给实现用作全局命名空间中的名称。
此外,标准库保留了在“namespace std”中定义的所有名称和一些僵尸名称;请参见[reserved.names.general]
关于POSIX呢?
正如接受的答案所指出的,可能还有其他部分的实现,比如POSIX标准,会限制您可以使用的标识符。
每个在头部描述中具有文件作用域的标识符,如果包含了该头文件,则保留为具有相同命名空间的文件作用域标识符使用。 任何头文件 [保留] 后缀 _t - POSIX 2008标准,2.2.2 在C++中,几乎所有与POSIX相关的问题都可以通过命名空间来避免。 这也是为什么C++标准可以添加大量的符号,如std::enable_if_t,而不会破坏POSIX兼容性的原因。

可视化

int x;      // OK
int x_;     // OK
int _x;     // RESERVED
int x__;    // RESERVED (OK in C)
int __x;    // RESERVED
int _X;     // RESERVED
int assert; // RESERVED (macro name)
int x_t;    // RESERVED (only by POSIX)

namespace {
int y;      // OK
int y_;     // OK
int _y;     // OK
int y__;    // RESERVED (OK in C, ignoring namespaces)
int __y;    // RESERVED
int _Y;     // RESERVED
int assert; // RESERVED (macro name)
int y_t;    // OK
}

上述对于y的规则适用于命名空间和未命名空间。 无论哪种情况,在下面的命名空间中,全局命名空间的规则不再适用(参见[namespace.unnamed])。
上述对于y的规则也适用于类、函数等标识符;除了全局作用域之外的任何地方。
尽管assert在这里不像函数样式的宏那样使用,但该名称是保留的。这也是为什么proposal P2884考虑在C++26中将其作为关键字,并取得了一些成功

推荐做法

为了安全起见,始终避免使用双下划线,并始终避免以下划线开头的命名。 后者在某些情况下是可以接受的,但很难记住这些规则,最好还是安全第一。

那么_本身呢?

有些人使用_来表示某些变量或函数参数未被使用。然而,你可以通过以下方式避免这种情况:
void foo(T _) { /* ... */ }
// replace with:
void foo(T) { /* ... */ }

std::scoped_lock _{mutex};
// replace with:
std::scoped_lock lock{mutex};

你还可以将参数 p 强制转换为 void,例如 (void)p,如果你只是想消除关于 p 未使用的警告,并且需要与 C 兼容。请参阅 为什么将未使用的返回值强制转换为 void?

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