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

What are the rules about using an underscore in a C++ identifier?

提问人:Roger Lipscombe 提问时间:10/23/2008 最后编辑:Jan SchultkeRoger Lipscombe 更新时间:9/27/2023 访问量:336656

问:

在 C++ 中,使用某种前缀命名成员变量以表示它们是成员变量而不是局部变量或参数这一事实是很常见的。如果您具有 MFC 背景,则可能会使用 .我也偶尔看到过。m_foomyFoo

C#(或者可能只是 .NET)似乎建议只使用下划线,如 .C++ 标准允许这样做吗?_foo

C 语言-律师 命名-约定 C++-常见问题

评论

3赞 CesarB 10/23/2008
关于这一点的 glibc 手册页可以在 gnu.org/software/libc/manual/html_node/Reserved-Names.html Edit 中找到:另见 opengroup.org/onlinepubs/009695399/functions/xsh_chap02_02.html
10赞 g24l 11/17/2015
只是要注意,对这些规则的无知并不一定意味着您的代码不会编译或运行,但很可能您的代码无法移植到不同的编译器和版本,因为不能保证不会有名称冲突。为了支持这一点,我知道一个重要系统的某些实现,该系统一直使用_大写字母作为命名约定。因此没有错误。当然,这是不好的做法。

答:

2赞 John Millikin 10/23/2008 #1

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

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

评论

7赞 Martin York 10/23/2008
它们在图书馆中很常见。它们在用户代码中不应常见。
61赞 John Millikin 10/24/2008
你知道,人们确实用 C 语言编写库。
12赞 sbi 7/9/2015
“是的,下划线可以在标识符中的任何位置使用。”这对于全局标识符是错误的。请看罗杰的回答
1赞 BenW 6/16/2020
@sbi 根据 C 和 C++ 标准,是的,从语义上讲,保留了带有前导下划线的全局标识符。不过,它们是语法上有效的标识符,编译器不会阻止您命名函数,尽管这样做会依赖非标准的实现细节,因此您的代码可能会被语言/标准库实现/操作系统的未来版本破坏。_Foo
0赞 sbi 7/12/2020
@BenW:TTBOMK,C++标准只是说不允许使用以下划线开头的全局标识符,而没有对语法和语义进行任何区分。(还有任何以下划线后跟大写字母开头的标识符,以及具有两个连续下划线的标识符。
50赞 Roger Lipscombe 10/23/2008 #2

来自 MSDN

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

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

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

另请参阅此问题

评论

2赞 paercebal 6/28/2011
我在n3092.pdf(C++ 标准的草案)中找到了类似的文本:“17.6.3.3.2 全局名称”
9赞 hyde 10/4/2014
有趣的是,这似乎是唯一对这个问题有直接、简洁答案的答案。
11赞 sbi 7/9/2015
@hyde:实际上,事实并非如此,因为它跳过了规则,即在全局命名空间中没有任何带有前导下划线的标识符。请看罗杰的回答。我非常警惕引用MS VC文档作为C++标准的权威。
5赞 sbi 7/13/2015
首先,我仍然认为缺乏任何提示,表明相同的规则不适用于全局命名空间是失败的。然而,更糟糕的是,相邻的下划线不仅在标识符的开头,而且在标识符中的任何位置都被禁止。因此,这个答案不仅仅是遗漏了一个事实,而是实际上至少提出了一个错误的主张。正如我所说,除非问题完全是关于 VC,否则我不会参考 MSVC 文档。
1赞 Swift - Friday Pie 2/2/2017
@sbi具有讽刺意味的是,VC 符合 ISO C++,该 ISO 也保留了带有单下划线的名称,同时重命名了一些 posix 函数,例如 _dup() 而不是 dup()
954赞 25 revs, 14 users 35%Roger Pate #3

规则(在 C++11 中没有更改):

  • 保留在任何作用域中,包括用作实现宏:
    • 以下划线开头的标识符,后跟大写字母
    • 包含相邻下划线(或“双下划线”)的标识符
  • 保留在全局命名空间中:
    • 以下划线开头的标识符
  • 此外,命名空间中的所有内容都是保留的。(不过,您可以添加模板专用化。std

来自 2003 C++ 标准:

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

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

  • 每个包含双下划线 () 或以下划线开头后跟大写字母 (2.11) 的名称都保留给实现以供任何使用。__
  • 每个以下划线开头的名称都保留给实现,以用作全局命名空间中的名称。165

165) 此类名称也保留在命名空间 (17.4.3.1) 中。::std

C++ 语言基于 C 语言(1.1/2,C++03),而 C99 是规范参考(1.2/1,C++03),因此了解 1999 年 C 标准中的限制很有用(尽管它们不直接适用于 C++):

7.1.3 保留标识符

每个标头声明或定义其关联子句中列出的所有标识符,以及 (可选)声明或定义其关联的“未来库方向”子句中列出的标识符和标识符,这些标识符始终保留用于任何用途或用作文件范围标识符。

  • 以下划线和大写字母或其他字母开头的所有标识符 下划线始终保留用于任何用途。
  • 所有以下划线开头的标识符始终保留用作标识符 在普通名称空间和标记名称空间中都具有文件范围。
  • 以下任何子句中的每个宏名称(包括将来的库) directions) 保留供指定使用(如果包含其任何关联标头); 除非另有明确说明(见7.1.4)。
  • 以下任何子条款中具有外部链接的所有标识符(包括 未来的库方向)始终保留用作外部标识符 联动。154
  • 在以下任何子句中列出的具有文件范围的每个标识符(包括 future library directions) 保留用作宏名称和标识符 同一命名空间中的文件范围(如果包含其任何关联的标头)。

不保留其他标识符。如果程序在 保留的上下文(7.1.4 允许的上下文除外)或定义保留的上下文 identifier 作为宏名称,则行为未定义。

如果程序删除 (with ) 第一个标识符的任何宏定义 组,则行为未定义。#undef

154) 具有外部链接的保留标识符列表包括 、 、 和 。errnomath_errhandlingsetjmpva_end

其他限制可能适用。例如,POSIX标准保留了许多可能出现在普通代码中的标识符:

  • 以大写字母开头的名称后跟数字或大写字母:E
  • 可用于其他错误代码名称。
  • 以小写字母开头或后跟小写字母的名称isto
  • 可用于附加字符测试和转换功能。
  • 以后跟大写字母开头的名称LC_
  • 可用于指定区域设置属性的其他宏。
  • 以 或 为后缀的所有现有数学函数的名称fl
  • 用于分别对 float 和 long double 参数进行操作的相应函数。
  • 保留以后跟大写字母开头的名称SIG
  • 以获取其他信号名称。
  • 保留以后跟大写字母开头的名称SIG_
  • 用于其他信号操作。
  • 保留以 、 开头或后跟小写字母的名称strmemwcs
  • 用于其他字符串和数组函数。
  • 以任何小写字母开头或后跟任何小写字母的名称或保留的名称PRISCNX
  • 对于其他格式说明符宏
  • 以结尾的名称是保留的_t
  • 以获取其他类型名称。

虽然现在将这些名称用于您自己的目的可能不会造成问题,但它们确实增加了与该标准的未来版本发生冲突的可能性。


就我个人而言,我只是不下划线开始标识符。我的规则的新补充:不要在任何地方使用双下划线,这很容易,因为我很少使用下划线。

在对本文进行研究后,我不再以 POSIX 标准保留的标识符结束我的标识符。_t

关于任何标识符以结尾的规则让我感到非常惊讶。我认为这是一个 POSIX 标准(还不确定),正在寻找澄清和官方章节和诗句。这是来自 GNU libtool 手册,列出了保留名称。_t

CesarB 提供了以下指向 POSIX 2004 保留符号的链接,并指出“许多其他保留的前缀和后缀......可以在那里找到'。此处定义了 POSIX 2008 保留符号。这些限制比上述限制更加微妙。

评论

17赞 jalf 4/7/2009
C++ 标准不会“导入”C 标准,是吗?据我所知,它们导入某些标头,但不是整个语言或命名规则。但是,是的,_t也让我感到惊讶。但由于它是 C,它只能应用于全局 ns。当我阅读它时,在课堂上使用_t应该是安全的
28赞 Johannes Schaub - litb 9/20/2009
C++ 标准不会“导入”C 标准。它引用了 C 标准。C++ 库介绍说“该库还提供标准 C 库的功能”。它通过包含带有适当更改的 C 标准库的标头来实现这一点,但不是通过“导入”它来实现的。C++ 标准有自己的一组描述保留名称的规则。如果在 C 中保留的名称应该在 C++ 中保留,那么这就是这样说的地方。但是C++标准并没有这么说。所以我不相信在 C 中保留的东西在 C++ 中保留 - 但我很可能是错的。
10赞 Johannes Schaub - litb 9/20/2009
这就是我发现的关于“_t”问题的内容:n1256 (C99 TC3) 说:“以 int 或 uint 开头并以 _t 结尾的 Typedef 名称”被保留。我认为这仍然允许使用像“foo_t”这样的名称 - 但我认为这些名称随后被 POSIX 保留。
70赞 Sjoerd 8/12/2010
所以 POSIX 保留了“tolerance”,因为它以“to”+小写字母开头?我敢打赌,很多代码都违反了这个规则!
24赞 Jonathan Wakely 8/29/2013
@LokiAstari,“C++标准是根据 C 标准定义的。基本上,它说C++是具有这些差异和补充的C。废话!C++ 仅引用 [basic.fundamental] 和库中的 C 标准。如果你说的是真的,那么 C++ 在哪里这么说,并且在 C++ 中不存在?C++语言是显式定义的,而不是根据对C语言的“编辑”来定义的,否则标准可能会更短!_Bool_Imaginary
222赞 paercebal 10/23/2008 #4

避免名称冲突的规则既在 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]

不以下划线开头的文字后缀标识符保留用于将来的标准化。

最后一个子句令人困惑,除非您认为如果在全局命名空间中定义,则以一个下划线开头并后跟小写字母的名称将是 Ok......

评论

11赞 paercebal 1/18/2012
@Meysam :包含两个连续的下划线(开头两个,结尾两个),所以根据标准这是错误的。__WRONG_AGAIN__
8赞 paercebal 12/5/2013
@B Јовић :包含两个连续的下划线(中间两个),所以根据标准这是错误的WRONG__WRONG
3赞 Ruslan 9/6/2016
将代码放在唯一的命名空间中也有助于避免冲突:但这仍然不够,因为标识符可能与关键字发生冲突,而不管范围如何(例如 对于 GCC)。__attribute__
1赞 Jason S 9/13/2017
为什么按照标准,中间有两个连续的下划线有问题?用户定义的文本后缀适用于文本值,如 或 ;IIRC 这是指 ohttp://en.cppreference.com/w/cpp/language/user_literal1234567L4.0f
5赞 paercebal 9/14/2017
Why is there any problem of having two consecutive underscores in the middle according to the standard?因为标准说这些是保留的。这不是关于风格好坏的建议。这是标准的决定。他们为什么决定这样做?我猜第一批编译器在标准化之前就已经非正式地使用了这些约定。
29赞 Max Lybbert 11/15/2008 #5

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

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

0赞 Jan Schultke 9/4/2023 #6

首先,现行工作草案中的规则列于[lex.name] p3

此外,某些显示为标记或预处理标记的标识符保留供 C++ 实现使用,不得以其他方式使用;无需诊断。

  • 每个包含双下划线或以下划线开头后跟大写字母的标识符都保留给实现以供任何使用。__
  • 每个以下划线开头的标识符都保留给实现,以用作全局命名空间中的名称。

此外,标准库保留了 中定义的所有名称和一些僵尸名称;请参阅 [reserved.names.general]。namespace std

POSIX呢?

正如公认的答案所指出的,实现中可能还有其他部分,例如 POSIX 标准,它们限制了您可以使用的标识符。

如果包含标头,则标头部分中描述的每个具有文件范围的标识符都保留为在同一命名空间中具有文件范围的标识符。

ANY 标头 [reserves] 后缀 _t

- POSIX 2008 标准,2.2.2

在 C++ 中,几乎所有与 POSIX 相关的问题都可以通过命名空间来避免。 这也是为什么 C++ 标准可以在不破坏 POSIX 兼容性的情况下添加大量符号的原因。std::enable_if_t

可视化

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
}

上述规则适用于命名命名空间和未命名命名空间。 无论哪种方式,在以下命名空间中,全局命名空间的规则 不再适用(请参阅 [namespace.unnamed])。y

上述规则也适用于类、函数等中的标识符;除全局范围外的任何东西。y

尽管此处没有像函数样式宏那样使用,但该名称是保留的。这也是为什么提案 P2884 考虑将其作为 C++26 中的关键字,到目前为止取得了一些成功assert

推荐做法

为了安全起见,请始终避免使用双下划线,并始终避免使用带有前导下划线的 nam。 后者在某些情况下是可以的,但很难记住这些规则,安全总比后悔好。

那本身呢?_

有些人用来表示不使用某些变量或函数参数。但是,您可以通过以下方式避免这种情况:_

void foo(T _) { /* ... */ }
// replace with:
void foo(T) { /* ... */ }

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

您还可以将参数转换为 like ,如果这是关于静音有关未使用的警告,并且您需要 C 兼容性。请参阅为什么将未使用的返回值强制转换为 void?pvoid(void)pp