Google C++ Style Guide
Background 背景
C++ is one of the main development languages used by many of Google's open-source projects. As every C++ programmer knows, the language has many powerful features, but this power brings with it complexity, which in turn can make code more bug-prone and harder to read and maintain.
C++是许多谷歌开源项目使用的主要开发语言之一。正如每位C++程序员所知,该语言拥有许多强大功能,但这种能力也带来了复杂性,反过来可能导致代码更容易产生错误,且难以阅读和维护。
The goal of this guide is to manage this complexity by describing in detail the dos and don'ts of writing C++ code. These rules exist to keep the codebase manageable while still allowing coders to use C++ language features productively.
本指南的目标是通过详细描述编写C++代码的注意事项来管理这种复杂性。制定这些规则旨在保持代码库的可维护性,同时仍允许开发者高效利用C++语言特性。
Style, also known as readability, is what we call the conventions that govern our C++ code. The term Style is a bit of a misnomer, since these conventions cover far more than just source file formatting.
风格,亦常被称为可读性,即我们所指的用于规范C++代码的约定准则。"风格"这一称谓或许略显片面,因为相关规范所涵盖的范围远不止源代码文件的格式化方式。
Most open-source projects developed by Google conform to the requirements in this guide.
由谷歌开发的大多数开源项目均遵循本指南的要求。
Note that this guide is not a C++ tutorial: we assume that the reader is familiar with the language.
需要说明的是,本指南并非C++教程:我们默认读者已熟悉该语言。
Goals of the Style Guide 风格指南的目标
Why do we have this document?
我们为什么需要这份文档?
There are a few core goals that we believe this guide should serve. These are the fundamental whys that underlie all of the individual rules. By bringing these ideas to the fore, we hope to ground discussions and make it clearer to our broader community why the rules are in place and why particular decisions have been made. If you understand what goals each rule is serving, it should be clearer to everyone when a rule may be waived (some can be), and what sort of argument or alternative would be necessary to change a rule in the guide.
我们认为本指南应服务于若干核心目标。这些正是所有具体规则背后的根本依据。通过突出这些核心理念,我们希望让讨论有据可依,并使我们更广泛的社区更清楚地理解:规则为何存在、特定决策因何形成。若您能理解每条规则所服务的目标,那么当某条规则可能需要被放宽时(部分规则确实允许例外),每个人都会更清晰地认识到需要怎样的论证或替代方案才能推动指南中规则的修改。
The goals of the style guide as we currently see them are as follows:
我们认为当前风格指南的核心目标如下:
- Style rules should pull their weight
- 风格规则必须切实有效
- The benefit of a style rule
must be large enough to justify asking all of our engineers to
remember it. The benefit is measured relative to the codebase we would
get without the rule, so a rule against a very harmful practice may
still have a small benefit if people are unlikely to do it
anyway. This principle mostly explains the rules we don't have, rather
than the rules we do: for example,
gotocontravenes many of the following principles, but is already vanishingly rare, so the Style Guide doesn't discuss it. - 每条风格规则带来的收益必须足够大,才能值得要求所有工程师牢记。收益的衡量是相对于没有该规则时的代码库状态而言的,因此,一项针对极有害做法的规则,如果人们反正也不太可能那样做,其收益可能仍然很小。这条原则主要解释了我们为何不设立某些规则,而非我们设立了哪些规则:例如,
goto违反后面多条原则,但它已极其罕见,因此本《风格指南》不予讨论。 - Optimize for the reader, not the writer
- 优化阅读体验,而非编写体验
- Our codebase (and most individual components submitted to it) is
expected to continue for quite some time. As a result, more time will
be spent reading most of our code than writing it. We explicitly
choose to optimize for the experience of our average software engineer
reading, maintaining, and debugging code in our codebase rather than
ease when writing said code. "Leave a trace for the reader" is a
particularly common sub-point of this principle: When something
surprising or unusual is happening in a snippet of code (for example,
transfer of pointer ownership), leaving textual hints for the reader
at the point of use is valuable (
std::unique_ptrdemonstrates the ownership transfer unambiguously at the call site). - 我们的代码库(以及提交至其中的大多数独立组件)预期将持续运行相当长的时间。因此,花费在阅读我们大部分代码上的时间将超过编写它的时间。我们明确选择优化普通软件工程师在代码库中阅读、维护和调试代码的体验,而非追求编写这些代码时的便捷性。“为读者留下线索”是这一原则中特别常见的要点:当一段代码中发生令人惊讶或不寻常的情况时(例如,指针所有权的转移),在使用点为此处留下文本提示对读者很有价值(
std::unique_ptr就在调用点清晰表明了所有权转移)。 - Be consistent with existing code
- 与现有代码保持一致
- Using one style consistently through our codebase lets us focus on
other (more important) issues. Consistency also allows for automation:
tools that format your code or adjust your
#includes only work properly when your code is consistent with the expectations of the tooling. In many cases, rules that are attributed to "Be Consistent" boil down to "Just pick one and stop worrying about it"; the potential value of allowing flexibility on these points is outweighed by the cost of having people argue over them. However, there are limits to consistency; it is a good tie breaker when there is no clear technical argument, nor a long-term direction. It applies more heavily locally (per file, or for a tightly-related set of interfaces). Consistency should not generally be used as a justification to do things in an old style without considering the benefits of the new style, or the tendency of the codebase to converge on newer styles over time. - 在整个代码库中始终如一地使用同一种风格,能让我们专注于其他(更重要的)问题。一致性还为自动化提供了可能:格式化代码或调整
#include的工具,只有当您的代码符合工具的预期时才能正常工作。在许多情况下,那些归结为“保持一致”的规则,本质上就是“只管选一种然后别再为此纠结”;在这些点上允许灵活性所带来的潜在价值,远远比不上让人们为此争论不休所付出的代价。然而,一致性也有其限度;当没有明确的技术论据或长期方向时,它是一个很好的决胜因素。一致性在局部(例如单个文件内,或一组紧密相关的接口中)应用得更为严格。通常不应以一致性为由,不加考虑地沿用旧风格,而忽略新风格的优点,或者无视代码库随时间推移而趋向新风格的趋势。 - Be consistent with the broader C++ community when appropriate
- 在适当情况下与更广泛的C++社区保持一致
- Consistency with the way other organizations use C++ has value for the same reasons as consistency within our codebase. If a feature in the C++ standard solves a problem, or if some idiom is widely known and accepted, that's an argument for using it. However, sometimes standard features and idioms are flawed, or were just designed without our codebase's needs in mind. In those cases (as described below) it's appropriate to constrain or ban standard features. In some cases we prefer a homegrown or third-party library over a library defined in the C++ Standard, either out of perceived superiority or insufficient value to transition the codebase to the standard interface.
- 与其他组织使用C++的方式保持一致,其价值与我们代码库内部保持一致的价值原因相同。如果C++标准中的某项特性解决了问题,或者某种惯用法广为人知并被普遍接受,这就是使用它的有力论据。然而,有时标准特性和惯用法存在缺陷,或者其设计并未充分考虑我们代码库的需求。在这些情况下(如下文所述),限制或禁止使用标准特性是合理的。在某些情况下,我们更倾向于使用自研或第三方库,而非C++标准中定义的库,这可能是出于对其优越性的判断,也可能是认为将代码库迁移到标准接口的价值不足。
- Avoid surprising or dangerous constructs
- 避免意外或危险的构造
- C++ has features that are more surprising or dangerous than one might think at a glance. Some style guide restrictions are in place to prevent falling into these pitfalls. There is a high bar for style guide waivers on such restrictions, because waiving such rules often directly risks compromising program correctness.
- C++包含一些比初看起来更令人意外或更具危险性的特性。风格指南中的部分限制条款正是为了防止开发者落入这些陷阱。对此类限制的豁免标准定得非常高,因为豁免这些规则通常会直接危及程序的正确性。
- Avoid constructs that our average C++ programmer would find tricky or hard to maintain
- 避免使用普通C++程序员认为费解或难以维护的构造
- C++ has features that may not be generally appropriate because of the complexity they introduce to the code. In widely used code, it may be more acceptable to use trickier language constructs, because any benefits of more complex implementation are multiplied widely by usage, and the cost in understanding the complexity does not need to be paid again when working with new portions of the codebase. When in doubt, waivers to rules of this type can be sought by asking your project leads. This is specifically important for our codebase because code ownership and team membership changes over time: even if everyone that works with some piece of code currently understands it, such understanding is not guaranteed to hold a few years from now.
- C++ 中的某些特性可能因其给代码带来的复杂性而不太适合普遍使用。但在广泛使用的代码中,使用更复杂的语言结构可能更为可行,因为更复杂实现所带来的益处会通过广泛使用而放大,且理解复杂性的成本在接触代码库的新部分时无需重复付出。如有疑问,可向项目负责人申请豁免此类规则。这对我们的代码库尤为重要,因为代码所有权和团队成员会随时间变化:即便当前所有相关开发人员都能理解某段代码,也不能保证几年后依然如此。
- Be mindful of our scale
- 考量我们的规模因素
- With a codebase of 100+ million lines and thousands of engineers, some mistakes and simplifications for one engineer can become costly for many. For instance it's particularly important to avoid polluting the global namespace: name collisions across a codebase of hundreds of millions of lines are difficult to work with and hard to avoid if everyone puts things into the global namespace.
- 在拥有超过一亿行代码和数千名工程师的代码库中,个别工程师的某些错误或简化决策,可能会对众多其他人造成高昂代价。例如,尤其需要避免污染全局命名空间:在数亿行规模的代码库中,如果每个人都把内容放入全局命名空间,命名冲突将难以处理且不可避免。
- Concede to optimization when necessary
- 必要时为优化让步
- Performance optimizations can sometimes be necessary and appropriate, even when they conflict with the other principles of this document.
- 性能优化有时是必要且合理的,即使它们与本文件的其他原则相冲突。
The intent of this document is to provide maximal guidance with reasonable restriction. As always, common sense and good taste should prevail. By this we specifically refer to the established conventions of the entire Google C++ community, not just your personal preferences or those of your team. Be skeptical about and reluctant to use clever or unusual constructs: the absence of a prohibition is not the same as a license to proceed. Use your judgment, and if you are unsure, please don't hesitate to ask your project leads to get additional input.
本文档旨在通过合理的限制提供最大程度的指导。一如既往,常识与良好的品味应占据主导。这里我们特指整个谷歌C++社区既定的惯例,而非您个人或所在团队的偏好。对于巧妙或特殊的构造应持审慎态度并谨慎使用:未加禁止并不等同于获得了实施的许可。请运用您的判断力,如有不确定之处,请务必咨询您的项目负责人以获取更多意见。
C++ Version C++版本
Currently, code should target C++20, i.e., should not use C++23 features. The C++ version targeted by this guide will advance (aggressively) over time.
目前,代码应针对C++20,即不应使用C++23的特性。本指南的目标C++版本将随时间推移(积极地)向前发展。
Do not use non-standard extensions.
不要使用非标准扩展。
Consider portability to other environments before using features from C++17 and C++20 in your project.
在项目中使用 C++17 和 C++20 的特性之前,请先考虑其在其他环境中的可移植性。
Header Files 头文件
In general, every .cc file should have an
associated .h file. There are some common
exceptions, such as unit tests and small .cc files containing
just a main() function.
一般来说,每个 .cc 文件都应有一个对应的 .h 文件。也有一些常见的例外情况,例如单元测试,以及仅包含一个 main() 函数的小型 .cc 文件。
Correct use of header files can make a huge difference to the readability, size and performance of your code.
正确使用头文件,能极大提升代码的可读性,并影响代码的体积和性能。
The following rules will guide you through the various pitfalls of using header files.
以下规则将引导你规避使用头文件时可能遇到的各种陷阱。
Self-contained Headers 自包含头文件
Header files should be self-contained (compile on their own) and
end in .h. Non-header files that are meant for inclusion
should end in .inc and be used sparingly.
头文件应当是自包含的(能够独立编译),并以 .h 结尾。用于被包含的非头文件应以 .inc 结尾,并应谨慎使用。
All header files should be self-contained. Users and refactoring tools should not have to adhere to special conditions to include the header. Specifically, a header should have header guards and include all other headers it needs.
所有头文件都应当是自包含的。用户和重构工具不应需要遵循特殊条件才能包含该头文件。具体来说,头文件应当有头文件保护,并包含其所需的所有其他头文件。
When a header declares inline functions or templates that clients of the
header will instantiate, the inline functions and templates must also have
definitions in the header, either directly or in files it includes. Do not move
these definitions to separately included header (-inl.h) files;
this practice was common in the past, but is no longer allowed. When all
instantiations of a template occur in one .cc file, either because
they're
explicit or because the definition is accessible to only
the .cc file, the template definition can be kept in that file.
当头文件声明了内联函数或模板,且该头文件的使用方会对它们进行实例化时,这些内联函数和模板也必须在头文件中提供定义,可以直接提供,也可以放在其所包含的文件中。不要把这些定义移动到单独被包含的头文件(-inl.h)中;这种做法过去很常见,但现在已不再允许。当某个模板的所有实例化都发生在同一个 .cc 文件中(要么因为它们是显式实例化的,要么因为定义只对该 .cc 文件可见)时,模板定义可以保留在该文件中。
There are rare cases where a file designed to be included is not
self-contained. These are typically intended to be included at unusual
locations, such as the middle of another file. They might not
use header guards, and might not include
their prerequisites. Name such files with the .inc
extension. Use sparingly, and prefer self-contained headers when
possible.
在少数情况下,某个设计为被包含的文件并不是自包含的。这类文件通常是为了在不寻常的位置被包含,比如在另一个文件的中间。它们可能不会使用头文件保护,也可能不会包含其先决依赖。此类文件请使用 .inc 扩展名命名。应谨慎使用,并在可能的情况下优先选择自包含的头文件。
The #define Guard #define保护
All header files should have #define guards to
prevent multiple inclusion. The format of the symbol name
should be
<PROJECT>_<PATH>_<FILE>_H_.
所有头文件都应当有 #define 保护,以防止被重复包含。符号名的格式应为<PROJECT>_<PATH>_<FILE>_H_。
To guarantee uniqueness, they should
be based on the full path in a project's source tree. For
example, the file foo/src/bar/baz.h in
project foo should have the following
guard:
为保证唯一性,它们应当基于项目源代码树中的完整路径来命名。例如,项目 foo 中的文件 foo/src/bar/baz.h 应当使用如下的保护:
#ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_BAZ_H_
Include What You Use
If a source or header file refers to a symbol defined elsewhere, the file should directly include a header file which properly intends to provide a declaration or definition of that symbol. It should not include header files for any other reason.
如果某个源文件或头文件引用了在其他地方定义的符号,那么该文件应当直接包含一个明确用于提供该符号声明或定义的头文件。不应出于其他任何原因包含头文件。
Do not rely on transitive inclusions. This allows people to remove
no-longer-needed #include statements from their headers without
breaking clients. This also applies to related headers
- foo.cc should include bar.h if it uses a
symbol from it even if foo.h
includes bar.h.
不要依赖传递性包含。这使得人们可以从其头文件中移除不再需要的 #include 语句,而不会破坏使用方。这同样适用于相关头文件——如果 foo.cc 使用了 bar.h 中的符号,那么它就应当包含 bar.h,即使 foo.h 已经包含了 bar.h。
Forward Declarations 前向声明
Avoid using forward declarations where possible. Instead, include the headers you need.
尽可能避免使用前置声明。相反,请包含你所需的头文件。
A "forward declaration" is a declaration of an entity without an associated definition.
“前置声明”是指对某个实体的声明,但没有与之对应的定义。
// In a C++ source file: class B; void FuncInB(); extern int variable_in_b; ABSL_DECLARE_FLAG(flag_in_b);
- Forward declarations can save compile time, as
#includes force the compiler to open more files and process more input. - 前置声明可以节省编译时间,因为
#include会迫使编译器打开更多文件并处理更多输入。 - Forward declarations can save on unnecessary
recompilation.
#includes can force your code to be recompiled more often, due to unrelated changes in the header. - 前置声明可以避免不必要的重新编译。由于头文件中无关的改动,
#include可能会迫使你的代码更频繁地被重新编译。
- Forward declarations can hide a dependency, allowing user code to skip necessary recompilation when headers change.
- 前置声明可以隐藏依赖关系,允许用户代码在头文件更改时跳过必要的重新编译。
- A forward declaration as opposed to an
#includestatement makes it difficult for automatic tooling to discover the module defining the symbol. - 使用前置声明而不是
#include语句,会使自动化工具难以发现定义该符号的模块。 - A forward declaration may be broken by subsequent changes to the library. Forward declarations of functions and templates can prevent the header owners from making otherwise-compatible changes to their APIs, such as widening a parameter type, adding a template parameter with a default value, or migrating to a new namespace.
- 前置声明可能会被库的后续改动破坏。对函数和模板的前置声明,会阻止头文件所有者对其 API 进行原本兼容的修改,例如放宽参数类型、添加带默认值的模板参数,或迁移到新的命名空间。
- Forward declaring symbols from namespace
std::yields undefined behavior. - 对命名空间
std::中的符号进行前置声明会导致未定义行为。 - It can be difficult to determine whether a forward
declaration or a full
#includeis needed. Replacing an#includewith a forward declaration can silently change the meaning of code:// b.h: struct B {}; struct D : B {}; // good_user.cc: #include "b.h" void f(B*); void f(void*); void test(D* x) { f(x); } // Calls f(B*)If the#includewas replaced with forward decls forBandD,test()would callf(void*). - 有时很难判断是需要前置声明还是需要完整的
#include。 用前置声明替换#include可能会在不报错的情况下改变代码的含义:// b.h: struct B {}; struct D : B {}; // good_user.cc: #include "b.h" void f(B*); void f(void*); void test(D* x) { f(x); } // Calls f(B*)如果把#include替换为对B和D的前置声明, 那么test()将会调用f(void*)。 - Forward declaring multiple symbols from a header
can be more verbose than simply
#includeing the header. - 从同一个头文件中对多个符号进行前置声明,
可能会比直接
#include该头文件更冗长。 - Structuring code to enable forward declarations (e.g., using pointer members instead of object members) can make the code slower and more complex.
- 为了支持前置声明而调整代码结构(例如,用指针成员替代对象成员)可能会使代码更慢且更复杂。
Try to avoid forward declarations of entities defined in another project.
尽量避免对在其他项目中定义的实体进行前置声明。
Defining Functions in Header Files 在头文件中定义函数
Include the definition of a function at its point of declaration in a header file only
when the definition is short. If the definition otherwise has a reason be in the header,
put it in an internal part of the file. If necessary to make the definition ODR-safe, mark
it with an inline specifier.
只有当函数定义很短时,才在头文件中将函数定义放在其声明处。如果函数定义因其他原因需要放在头文件中,请将其放到文件的内部部分。如有必要,为使该定义满足 ODR 安全性,请使用 inline 说明符进行标记。
Functions defined in header files are sometimes referred to as "inline functions", which is a somewhat overloaded term that refers to several distinct but overlapping situations:
在头文件中定义的函数有时被称为“内联函数”。不过这是一个含义略有重叠的术语,可能指代几种不同但相互交叉的情况:
- A textually inline symbol's definition is exposed to the reader at the point of declaration.
- 文本内联(textually inline)的符号,其定义在声明处对读者可见。
- A function or variable defined in a header file is expandable inline since its definition is available for inline expansion by the compiler, which can lead to more efficient object code.
- 在头文件中定义的函数或变量是可展开内联(expandable inline)的,因为其定义可供编译器进行 内联展开,从而可能生成更高效的目标代码。
- ODR-safe entities do not violate the "One Definition Rule", which often requires the inline keyword for things defined in header files .
- ODR 安全(ODR-safe)的实体不会违反 “单一定义规则(One Definition Rule)”,而对在头文件中定义的内容,往往需要使用 inline 关键字。
While functions tend to be a more common source of confusion, these definitions apply to variables as well, and so do the rules here.
尽管函数往往是更常见的困惑来源,但这些定义同样适用于变量,因此这里的规则也同样适用。
- Defining a function textually in-line reduces boilerplate code for simple functions like accessors and mutators.
- 将函数以文本内联的方式定义,可以减少诸如访问器、修改器等简单函数的样板代码。
- As noted above, function definitions in header files can lead to more efficient object code for small functions due to inline expansion by the compiler.
- 如上所述,头文件中的函数定义可能会因编译器的内联展开而为小函数生成更高效的目标代码。
- Function templates and
constexprfunctions generally need to be defined in the header file that declares them (but not necessarily the public part). - 函数模板和
constexpr函数通常需要在声明它们的头文件中定义(但不一定在公开部分)。
- Embedding a function definition in the public API makes the API harder to skim, and incurs cognitive overhead for readers of that API- the more complex the function the higher the cost.
- 在公共 API 中嵌入函数定义会使 API 更难以快速浏览,并会给阅读该 API 的人带来认知负担——函数越复杂,成本越高。
- Public definitions expose implementation details that are at best harmless and often extraneous.
- 公共定义会暴露实现细节,这些细节往好了说无害,但往往多余。
Only define a function at its public declaration if it is short, say, 10 lines or fewer. Put
longer function bodies in the .cc file unless they must be in the header for
performance or technical reasons.
只有当函数很短时(比如说,10行或更少),才在函数的公开声明处定义它。将较长的函数体放在 .cc 文件中,除非出于性能或技术原因必须放在头文件中。
Even if a definition must be in the header, this is not a sufficient reason to put it within
the public part. Instead, the definition can be in an internal part of the header, such as the
private section of a class, within a namespace that includes the word
internal, or below a comment like // Implementation details only
below here.
即使定义必须放在头文件中,这也不是将其放在公开部分中的充分理由。相反,定义可以放在头文件的内部部分,例如类的 private 部分、包含 internal 一词的命名空间中,或在类似 // Implementation details only below here 这样的注释下方。
Once a definition is in a header file, it must be ODR-safe by having the inline
specifier or being implicitly specified inline by being a function template or defined in a class
body when first declared.
一旦定义放在头文件中,它必须通过具有 inline 说明符或者通过作为函数模板或在首次声明时在类体内定义而隐式指定为内联来确保 ODR 安全性。
template <typename T>
class Foo {
public:
int bar() { return bar_; }
void MethodWithHugeBody();
private:
int bar_;
};
// Implementation details only below here
template <typename T>
void Foo<T>::MethodWithHugeBody() {
...
}
Names and Order of Includes 头文件名称和包含顺序
Include headers in the following order: Related header, C system headers, C++ standard library headers, other libraries' headers, your project's headers.
按以下顺序包含头文件:相关头文件、C 系统头文件、C++ 标准库头文件、其他库的头文件、项目的头文件。
All of a project's header files should be
listed as descendants of the project's source
directory without use of UNIX directory aliases
. (the current directory) or ..
(the parent directory). For example,
google-awesome-project/src/base/logging.h
should be included as:
项目的所有头文件都应列为项目源目录的子项,且不得使用UNIX目录别名 .(当前目录)或 ..(上级目录)。例如,google-awesome-project/src/base/logging.h应被包含为:
#include "base/logging.h"
Headers should only be included using an angle-bracketed path if the library requires you to do so. In particular, the following headers require angle brackets:
只有当库要求这样做时,才应该使用尖括号路径包含头文件。特别是,以下头文件需要使用尖括号:
- C and C++ standard library headers (e.g.,
<stdlib.h>and<string>). - C 和 C++ 标准库头文件(例如,
<stdlib.h>和<string>)。 - POSIX, Linux, and Windows system headers (e.g.,
<unistd.h>and<windows.h>). - POSIX、Linux 和 Windows 系统头文件(例如,
<unistd.h>和<windows.h>)。 - In rare cases, third_party libraries (e.g.,
<Python.h>). - 在极少数情况下,第三方库(例如,
<Python.h>)。
In dir/foo.cc or
dir/foo_test.cc, whose main
purpose is to implement or test the stuff in
dir2/foo2.h, order your includes
as follows:
dir2/foo2.h.- A blank line
- C system headers, and any other headers in angle brackets with the
.hextension, e.g.,<unistd.h>,<stdlib.h>,<Python.h>. - A blank line
- C++ standard library headers (without file extension), e.g.,
<algorithm>,<cstddef>. - A blank line
- Other libraries'
.hfiles. - A blank line
-
Your project's
.hfiles.
在 dir/foo.cc 或
dir/foo_test.cc 中,其主要
目的是实现或测试
dir2/foo2.h 中的内容,按以下顺序包含
头文件:
dir2/foo2.h。- 空行
- C 系统头文件,以及其他带
.h扩展名且使用尖括号的头文件,例如,<unistd.h>、<stdlib.h>、<Python.h>。 - 空行
- C++ 标准库头文件(无文件扩展名),例如,
<algorithm>、<cstddef>。 - 空行
- 其他库的
.h文件。 - 空行
-
项目的
.h文件。
Separate each non-empty group with one blank line.
用一个空行分隔每个非空组。
With the preferred ordering, if the related header
dir2/foo2.h omits any necessary
includes, the build of dir/foo.cc
or dir/foo_test.cc will break.
Thus, this rule ensures that build breaks show up first
for the people working on these files, not for innocent
people in other packages.
通过这种首选的排序方式,如果相关头文件
dir2/foo2.h 遗漏了任何必要的
包含,dir/foo.cc
或 dir/foo_test.cc 的构建将会失败。
因此,这条规则确保构建失败首先出现在
处理这些文件的人员那里,而不是其他
包中的无辜人员那里。
dir/foo.cc and
dir2/foo2.h are usually in the same
directory (e.g., base/basictypes_test.cc and
base/basictypes.h), but may sometimes be in different
directories too.
dir/foo.cc 和
dir2/foo2.h 通常在同一个
目录中(例如,base/basictypes_test.cc 和
base/basictypes.h),但有时也可能在不同
的目录中。
Note that the C headers such as stddef.h
are essentially interchangeable with their C++ counterparts
(cstddef).
Either style is acceptable, but prefer consistency with existing code.
注意,诸如 stddef.h 这样的
C头文件基本上可以与其C++对应版本
(cstddef)互换使用。
两种风格都可以接受,但最好与现有代码保持一致。
Within each section the includes should be ordered alphabetically. Note that older code might not conform to this rule and should be fixed when convenient.
在每个部分中,包含的头文件应该按 字母顺序排列。注意旧代码可能不符合 这条规则,应该在方便时修复。
For example, the includes in
google-awesome-project/src/foo/internal/fooserver.cc
might look like this:
例如,
google-awesome-project/src/foo/internal/fooserver.cc
中的包含可能看起来像这样:
#include "foo/server/fooserver.h" #include <sys/types.h> #include <unistd.h> #include <string> #include <vector> #include "base/basictypes.h" #include "foo/server/bar.h" #include "third_party/absl/flags/flag.h"
Exception:
例外:
Sometimes, system-specific code needs conditional includes. Such code can put conditional includes after other includes. Of course, keep your system-specific code small and localized. Example:
有时,系统特定的代码需要条件包含。这样的代码可以将条件包含放在其他包含之后。当然,要保持系统特定代码小而局部化。例如:
#include "foo/public/fooserver.h" #ifdef _WIN32 #include <windows.h> #endif // _WIN32
Scoping 作用域
Namespaces 命名空间
With few exceptions, place code in a namespace. Namespaces
should have unique names based on the project name, and possibly
its path. Do not use using-directives (e.g.,
using namespace foo). Do not use
inline namespaces. For unnamed namespaces, see
Internal Linkage.
除了少数例外情况,请将代码放在命名空间中。命名空间应具有基于项目名称及其路径的唯一名称。不要使用 using 指令(例如,using namespace foo)。不要使用内联命名空间。对于未命名的命名空间,请参见 内部链接。
Namespaces subdivide the global scope into distinct, named scopes, and so are useful for preventing name collisions in the global scope.
命名空间将全局作用域细分为不同的、命名的作用域,因此对于防止全局作用域中的名称冲突非常有用。
Namespaces provide a method for preventing name conflicts in large programs while allowing most code to use reasonably short names.
命名空间提供了一种防止大型程序中名称冲突的方法,同时允许大多数代码使用合理的短名称。
For example, if two different projects have a class
Foo in the global scope, these symbols may
collide at compile time or at runtime. If each project
places their code in a namespace, project1::Foo
and project2::Foo are now distinct symbols that
do not collide, and code within each project's namespace
can continue to refer to Foo without the prefix.
例如,如果两个不同的项目在全局作用域中都有一个类 Foo,这些符号可能会在编译时或运行时发生冲突。如果每个项目都将其代码放在命名空间中,project1::Foo 和 project2::Foo 现在就是互不冲突的不同符号,并且每个项目命名空间内的代码可以继续在不加前缀的情况下引用 Foo。
Inline namespaces automatically place their names in the enclosing scope. Consider the following snippet, for example:
内联命名空间会自动将其名称放入外层作用域中。例如,考虑以下片段:
namespace outer {
inline namespace inner {
void foo();
} // namespace inner
} // namespace outer
The expressions outer::inner::foo() and
outer::foo() are interchangeable. Inline
namespaces are primarily intended for ABI compatibility
across versions.
表达式 outer::inner::foo() 和 outer::foo() 是可以互换的。内联命名空间主要用于跨版本的 ABI 兼容性。
Namespaces can be confusing, because they complicate the mechanics of figuring out what definition a name refers to.
命名空间可能会令人困惑,因为它们使确定名称引用哪个定义的机制变得复杂。
Inline namespaces, in particular, can be confusing because names aren't actually restricted to the namespace where they are declared. They are only useful as part of some larger versioning policy.
特别是内联命名空间可能会令人困惑,因为名称实际上并不局限于声明它们的命名空间。它们仅作为某种更大的版本控制策略的一部分才有用。
In some contexts, it's necessary to repeatedly refer to symbols by their fully-qualified names. For deeply-nested namespaces, this can add a lot of clutter.
在某些上下文中,有必要重复使用全限定名称来引用符号。对于深层嵌套的命名空间,这可能会增加很多混乱。
Namespaces should be used as follows:
命名空间的使用应遵循以下原则:
- Follow the rules on Namespace Names.
- 遵循 命名空间名称 的规则。
- Terminate multi-line namespaces with comments as shown in the given examples.
- 如示例所示,用注释结束多行命名空间。
-
Namespaces wrap the entire source file after includes, gflags definitions/declarations and forward declarations of classes from other namespaces.
命名空间包裹包含文件之后的整个源文件、gflags 定义/声明以及来自其他命名空间的类的前置声明。
// In the .h file namespace mynamespace { // All declarations are within the namespace scope. // Notice the lack of indentation. class MyClass { public: ... void Foo(); }; } // namespace mynamespace// In the .cc file namespace mynamespace { // Definition of functions is within scope of the namespace. void MyClass::Foo() { ... } } // namespace mynamespaceMore complex
.ccfiles might have additional details, like flags or using-declarations.更复杂的
.cc文件可能包含更多细节,如标志或 using 声明。#include "a.h" ABSL_FLAG(bool, someflag, false, "a flag"); namespace mynamespace { using ::foo::Bar; ...code for mynamespace... // Code goes against the left margin. } // namespace mynamespace - To place generated protocol
message code in a namespace, use the
packagespecifier in the.protofile. See Protocol Buffer Packages for details. - 要将生成的 protocol message 代码放入命名空间中,请在
.proto文件中使用package说明符。详情请参阅 Protocol Buffer Packages。 - Do not declare anything in namespace
std, including forward declarations of standard library classes. Declaring entities in namespacestdis undefined behavior, i.e., not portable. To declare entities from the standard library, include the appropriate header file. - 不要在
std命名空间中声明任何内容,包括标准库类的前置声明。在std命名空间中声明实体是未定义行为,即不可移植。要声明标准库中的实体,请包含相应的头文件。 You may not use a using-directive to make all names from a namespace available.
不得使用 using 指令 使命名空间中的所有名称可用。
// Forbidden -- This pollutes the namespace. using namespace foo;
Do not use Namespace aliases at namespace scope in header files except in explicitly marked internal-only namespaces, because anything imported into a namespace in a header file becomes part of the public API exported by that file. Namespace aliases can be used when those conditions don't apply, but they must have appropriate names.
不要在头文件的命名空间作用域中使用命名空间别名,除非是在明确标记为仅内部使用的命名空间中,因为在头文件中导入命名空间的任何内容都会成为该文件导出的公共 API 的一部分。当这些条件不适用时,可以使用命名空间别名,但它们必须具有 适当的名称。
// In a .h file, an alias must not be a separate API, or must be hidden in an // implementation detail. namespace librarian { namespace internal { // Internal, not part of the API. namespace sidetable = ::pipeline_diagnostics::sidetable; } // namespace internal inline void my_inline_function() { // Local to a function. namespace baz = ::foo::bar::baz; ... } } // namespace librarian// Remove uninteresting parts of some commonly used names in .cc files. namespace sidetable = ::pipeline_diagnostics::sidetable;
- Do not use inline namespaces.
- 不要使用内联命名空间。
Use namespaces with "internal" in the name to document parts of an API that should not be mentioned by users of the API.
使用名称中包含 "internal" 的命名空间来记录 API 中不应由 API 用户提及的部分。
// We shouldn't use this internal name in non-absl code. using ::absl::container_internal::ImplementationDetail;
Note that there is still a risk of collision between libraries within a nested
internalnamespace, so give each library within a namespace a unique internal namespace by adding the library's filename. For example,gshoe/widget.hwould usegshoe::internal_widgetas opposed to justgshoe::internal.请注意,嵌套的
internal命名空间内的库之间仍然存在冲突风险,因此通过添加库的文件名,为命名空间内的每个库提供一个唯一的内部命名空间。例如,gshoe/widget.h将使用gshoe::internal_widget,而不是仅仅使用gshoe::internal。Single-line nested namespace declarations are preferred in new code, but are not required.
在编写新代码时,首选单行嵌套命名空间声明,但并非强制要求。
namespace my_project::my_component { ... } // namespace my_project::my_component
Internal Linkage 内部链接
When definitions in a .cc file do not need to be
referenced outside that file, give them internal linkage by placing
them in an unnamed namespace or declaring them static.
Do not use either of these constructs in .h files.
当 .cc 文件中的定义不需要在该文件之外被引用时,请通过将其放入未命名的命名空间或将其声明为 static 来赋予其内部链接属性。不要在 .h 文件中使用这两种构造。
All declarations can be given internal linkage by placing them in unnamed
namespaces. Functions and variables can also be given internal linkage by
declaring them static. This means that anything you're declaring
can't be accessed from another file. If a different file declares something with
the same name, then the two entities are completely independent.
所有的声明都可以通过放入未命名的命名空间来获得内部链接属性。函数和变量也可以通过声明为 static 来获得内部链接属性。这意味着你声明的任何内容都不能从另一个文件访问。如果另一个文件声明了同名的内容,那么这两个实体是完全独立的。
Use of internal linkage in .cc files is encouraged
for all code that does not need to be referenced elsewhere.
Do not use internal linkage in .h files.
对于所有不需要在其他地方引用的代码,鼓励在 .cc 文件中使用内部链接。不要在 .h 文件中使用内部链接。
Format unnamed namespaces like named namespaces. In the terminating comment, leave the namespace name empty:
像格式化命名命名空间一样格式化未命名的命名空间。在结束注释中,将命名空间名称留空:
namespace {
...
} // namespace
Nonmember, Static Member, and Global Functions 非成员函数、静态成员函数和全局函数
Prefer placing nonmember functions in a namespace; use completely global functions rarely. Do not use a class simply to group static members. Static methods of a class should generally be closely related to instances of the class or the class's static data.
优先将非成员函数放在命名空间中;尽量少使用完全全局函数。不要仅仅为了给静态成员分组而使用类。类的静态方法通常应该与类的实例或类的静态数据紧密相关。
Nonmember and static member functions can be useful in some situations. Putting nonmember functions in a namespace avoids polluting the global namespace.
非成员函数和静态成员函数在某些情况下很有用。将非成员函数放在命名空间中可以避免污染全局命名空间。
Nonmember and static member functions may make more sense as members of a new class, especially if they access external resources or have significant dependencies.
非成员函数和静态成员函数作为新类的成员可能更有意义,特别是当它们访问外部资源或有重要依赖时。
Sometimes it is useful to define a function not bound to a class instance. Such a function can be either a static member or a nonmember function. Nonmember functions should not depend on external variables, and should nearly always exist in a namespace. Do not create classes only to group static members; this is no different than just giving the names a common prefix, and such grouping is usually unnecessary anyway.
有时定义一个不绑定到类实例的函数是很有用的。这样的函数可以是静态成员函数,也可以是非成员函数。非成员函数不应依赖于外部变量,并且几乎总是应该存在于命名空间中。不要仅为了对静态成员进行分组而创建类;这与仅给名称加上公共前缀没有什么不同,而且这种分组通常也是不必要的。
If you define a nonmember function and it is only
needed in its .cc file, use
internal linkage to limit
its scope.
如果你定义了一个非成员函数,并且它只需要在它的 .cc 文件中使用,请使用 内部链接 来限制其作用域。
Local Variables 局部变量
Place a function's variables in the narrowest scope possible, and initialize variables in the declaration.
将函数的变量置于尽可能小的作用域内,并在声明时初始化变量。
C++ allows you to declare variables anywhere in a function. We encourage you to declare them in a scope as local as possible, and as close to the first use as possible. This makes it easier for the reader to find the declaration and see what type the variable is and what it was initialized to. In particular, initialization should be used instead of declaration and assignment, e.g.,:
C++ 允许你在函数中的任何位置声明变量。我们鼓励你在尽可能局部的作用域中声明它们,并尽可能靠近第一次使用的地方。这使得读者更容易找到声明,查看变量是什么类型以及它被初始化为什么。特别是,应该使用初始化而不是声明和赋值,例如:
int i; i = f(); // Bad -- initialization separate from declaration.
int i = f(); // Good -- declaration has initialization.
int jobs = NumJobs(); // More code... f(jobs); // Bad -- declaration separate from use.
int jobs = NumJobs(); f(jobs); // Good -- declaration immediately (or closely) followed by use.
std::vector<int> v; v.push_back(1); // Prefer initializing using brace initialization. v.push_back(2);
std::vector<int> v = {1, 2}; // Good -- v starts initialized.
Variables needed for if, while
and for statements should normally be declared
within those statements, so that such variables are confined
to those scopes. For example:
if、while 和 for 语句所需的变量通常应在这些语句中声明,以便这些变量被限制在这些作用域内。例如:
while (const char* p = strchr(str, '/')) str = p + 1;
There is one caveat: if the variable is an object, its constructor is invoked every time it enters scope and is created, and its destructor is invoked every time it goes out of scope.
有一个注意事项:如果变量是一个对象,那么每次进入作用域并创建它时都会调用其构造函数,每次离开作用域时都会调用其析构函数。
// Inefficient implementation:
for (int i = 0; i < 1000000; ++i) {
Foo f; // My ctor and dtor get called 1000000 times each.
f.DoSomething(i);
}
It may be more efficient to declare such a variable used in a loop outside that loop:
将循环中使用的此类变量声明在循环之外可能会更高效:
Foo f; // My ctor and dtor get called once each.
for (int i = 0; i < 1000000; ++i) {
f.DoSomething(i);
}
Static and Global Variables 静态和全局变量
Objects with static storage duration are forbidden unless they are trivially destructible. Informally this means that the destructor does not do anything, even taking member and base destructors into account. More formally it means that the type has no user-defined or virtual destructor and that all bases and non-static members are trivially destructible. Static function-local variables may use dynamic initialization. Use of dynamic initialization for static class member variables or variables at namespace scope is discouraged, but allowed in limited circumstances; see below for details.
除非是 可平凡析构(trivially destructible) 的对象,否则禁止使用具有 静态存储期(static storage duration) 的对象。通俗地说,这意味着析构函数不做任何事情,即使考虑到成员和基类的析构函数也是如此。更正式地说,这意味着该类型没有用户定义的或虚析构函数,并且所有基类和非静态成员都是可平凡析构的。函数内的静态变量可以使用动态初始化。对于静态类成员变量或命名空间作用域内的变量,不鼓励使用动态初始化,但在有限的情况下是允许的;详情见下文。
As a rule of thumb: a global variable satisfies these requirements if its
declaration, considered in isolation, could be constexpr.
作为一个经验法则:如果一个全局变量的声明在孤立状态下可以是 constexpr,那么它就满足这些要求。
Every object has a storage duration, which correlates with its
lifetime. Objects with static storage duration live from the point of their
initialization until the end of the program. Such objects appear as variables at
namespace scope ("global variables"), as static data members of classes, or as
function-local variables that are declared with the static
specifier. Function-local static variables are initialized when control first
passes through their declaration; all other objects with static storage duration
are initialized as part of program start-up. All objects with static storage
duration are destroyed at program exit (which happens before unjoined threads
are terminated).
每个对象都有一个 存储期,它与对象的生命周期相关。具有静态存储期的对象从其初始化点一直存活到程序结束。此类对象表现为命名空间作用域下的变量(“全局变量”)、类的静态数据成员,或者是声明带有 static 说明符的函数局部变量。函数局部静态变量在控制流首次经过其声明时初始化;所有其他具有静态存储期的对象在程序启动时初始化。所有具有静态存储期的对象在程序退出时销毁(发生在未合并的线程终止之前)。
Initialization may be dynamic, which means that something non-trivial happens during initialization. (For example, consider a constructor that allocates memory, or a variable that is initialized with the current process ID.) The other kind of initialization is static initialization. The two aren't quite opposites, though: static initialization always happens to objects with static storage duration (initializing the object either to a given constant or to a representation consisting of all bytes set to zero), whereas dynamic initialization happens after that, if required.
初始化可以是 动态的,这意味着在初始化期间会发生一些非平凡的操作。(例如,考虑一个分配内存的构造函数,或者一个用当前进程 ID 初始化的变量。)另一种初始化是 静态 初始化。不过这两者并不完全对立:静态初始化 总是 发生在具有静态存储期的对象上(将对象初始化为给定的常量或全零的表示),而动态初始化在此之后发生(如果需要的话)。
Global and static variables are very useful for a large number of applications: named constants, auxiliary data structures internal to some translation unit, command-line flags, logging, registration mechanisms, background infrastructure, etc.
全局和静态变量在许多应用中非常有用:命名常量、某些翻译单元内部的辅助数据结构、命令行标志、日志记录、注册机制、后台基础设施等。
Global and static variables that use dynamic initialization or have non-trivial destructors create complexity that can easily lead to hard-to-find bugs. Dynamic initialization is not ordered across translation units, and neither is destruction (except that destruction happens in reverse order of initialization). When one initialization refers to another variable with static storage duration, it is possible that this causes an object to be accessed before its lifetime has begun (or after its lifetime has ended). Moreover, when a program starts threads that are not joined at exit, those threads may attempt to access objects after their lifetime has ended if their destructor has already run.
使用动态初始化或具有非平凡析构函数的全局和静态变量会产生复杂性,容易导致难以发现的错误。动态初始化在翻译单元之间没有顺序,析构也没有(除了析构按初始化的相反顺序发生)。当一个初始化引用另一个具有静态存储期的变量时,可能会导致对象在其生命周期开始之前(或结束之后)被访问。此外,当程序启动未在退出时合并的线程时,如果对象的析构函数已经运行,这些线程可能会尝试在对象生命周期结束后访问它们。
Decision on destruction 关于析构的决定
When destructors are trivial, their execution is not subject to ordering at
all (they are effectively not "run"); otherwise we are exposed to the risk of
accessing objects after the end of their lifetime. Therefore, we only allow
objects with static storage duration if they are trivially destructible.
Fundamental types (like pointers and int) are trivially
destructible, as are arrays of trivially destructible types. Note that
variables marked with constexpr are trivially destructible.
当析构函数是平凡的时,它们的执行完全不受顺序的限制(它们实际上没有“运行”);否则,我们将面临在对象生命周期结束后访问对象的风险。因此,我们只允许具有静态存储期的对象是可平凡析构的。基本类型(如指针和 int)是可平凡析构的,可平凡析构类型的数组也是如此。请注意,标记为 constexpr 的变量是可平凡析构的。
const int kNum = 10; // Allowed
struct X { int n; };
const X kX[] = {{1}, {2}, {3}}; // Allowed
void foo() {
static const char* const kMessages[] = {"hello", "world"}; // Allowed
}
// Allowed: constexpr guarantees trivial destructor.
constexpr std::array<int, 3> kArray = {1, 2, 3};
// bad: non-trivial destructor
const std::string kFoo = "foo";
// Bad for the same reason, even though kBar is a reference (the
// rule also applies to lifetime-extended temporary objects).
const std::string& kBar = StrCat("a", "b", "c");
void bar() {
// Bad: non-trivial destructor.
static std::map<int, int> kData = {{1, 0}, {2, 0}, {3, 0}};
}
Note that references are not objects, and thus they are not subject to the
constraints on destructibility. The constraint on dynamic initialization still
applies, though. In particular, a function-local static reference of the form
static T& t = *new T; is allowed.
请注意,引用不是对象,因此它们不受可析构性约束的限制。但是,动态初始化的约束仍然适用。特别是,允许形式为 static T& t = *new T; 的函数局部静态引用。
Decision on initialization 关于初始化的决定
Initialization is a more complex topic. This is because we must not only consider whether class constructors execute, but we must also consider the evaluation of the initializer:
初始化是一个更复杂的话题。这是因为我们不仅必须考虑类构造函数是否执行,还必须考虑初始化器的求值:
int n = 5; // Fine int m = f(); // ? (Depends on f) Foo x; // ? (Depends on Foo::Foo) Bar y = g(); // ? (Depends on g and on Bar::Bar)
All but the first statement expose us to indeterminate initialization ordering.
除第一条语句外,所有语句都使我们面临不确定的初始化顺序。
The concept we are looking for is called constant initialization in
the formal language of the C++ standard. It means that the initializing
expression is a constant expression, and if the object is initialized by a
constructor call, then the constructor must be specified as
constexpr, too:
我们正在寻找的概念在 C++ 标准的正式语言中称为 常量初始化(constant initialization)。这意味着初始化表达式是一个常量表达式,如果对象是通过构造函数调用初始化的,那么该构造函数也必须指定为 constexpr:
struct Foo { constexpr Foo(int) {} };
int n = 5; // Fine, 5 is a constant expression.
Foo x(2); // Fine, 2 is a constant expression and the chosen constructor is constexpr.
Foo a[] = { Foo(1), Foo(2), Foo(3) }; // Fine
Constant initialization is always allowed. Constant initialization of
static storage duration variables should be marked with constexpr
or constinit.
Any non-local static storage
duration variable that is not so marked should be presumed to have
dynamic initialization, and reviewed very carefully.
常量初始化总是被允许的。静态存储期变量的常量初始化应标记为 constexpr 或 constinit。任何未被如此标记的非局部静态存储期变量都应被假定为具有动态初始化,并需仔细审查。
By contrast, the following initializations are problematic:
相比之下,以下初始化是有问题的:
// Some declarations used below.
time_t time(time_t*); // Not constexpr!
int f(); // Not constexpr!
struct Bar { Bar() {} };
// Problematic initializations.
time_t m = time(nullptr); // Initializing expression not a constant expression.
Foo y(f()); // Ditto
Bar b; // Chosen constructor Bar::Bar() not constexpr.
Dynamic initialization of nonlocal variables is discouraged, and in general it is forbidden. However, we do permit it if no aspect of the program depends on the sequencing of this initialization with respect to all other initializations. Under those restrictions, the ordering of the initialization does not make an observable difference. For example:
非局部变量的动态初始化是不鼓励的,通常也是被禁止的。但是,如果程序的任何方面都不依赖于该初始化相对于所有其他初始化的顺序,我们允许这样做。在这些限制下,初始化的顺序不会产生可观察到的差异。例如:
int p = getpid(); // Allowed, as long as no other static variable
// uses p in its own initialization.
Dynamic initialization of static local variables is allowed (and common).
静态局部变量的动态初始化是允许的(也很常见)。
Common patterns 常见模式
- Global strings: if you require a named global or static string constant,
consider using a
constexprvariable ofstring_view, character array, or character pointer, pointing to a string literal. String literals have static storage duration already and are usually sufficient. See TotW #140. - 全局字符串:如果你需要一个命名的全局或静态字符串常量,请考虑使用指向字符串字面量的
string_view、字符数组或字符指针的constexpr变量。字符串字面量已经具有静态存储期,通常就足够了。参见 TotW #140。 - Maps, sets, and other dynamic containers: if you require a static, fixed
collection, such as a set to search against or a lookup table, you cannot
use the dynamic containers from the standard library as a static variable,
since they have non-trivial destructors. Instead, consider
a simple array of trivial types, e.g., an array of arrays of ints (for a "map from int to
int"), or an array of pairs (e.g., pairs of
intandconst char*). For small collections, linear search is entirely sufficient (and efficient, due to memory locality); consider using the facilities from absl/algorithm/container.h for the standard operations. If necessary, keep the collection in sorted order and use a binary search algorithm. If you do really prefer a dynamic container from the standard library, consider using a function-local static pointer, as described below . - Map、Set 和其他动态容器:如果你需要一个静态的、固定的集合,例如用于搜索的 Set 或查找表,你不能将标准库中的动态容器用作静态变量,因为它们具有非平凡的析构函数。相反,请考虑使用简单类型的数组,例如 int 数组的数组(用于“从 int 到 int 的映射”),或 pair 的数组(例如,
int和const char*的 pair)。对于小型集合,线性搜索完全足够(并且由于内存局部性而高效);考虑使用 absl/algorithm/container.h 中的设施进行标准操作。如有必要,保持集合有序并使用二分搜索算法。如果你确实更喜欢标准库中的动态容器,请考虑使用函数局部的静态指针,如下所述。 - Smart pointers (
std::unique_ptr,std::shared_ptr): smart pointers execute cleanup during destruction and are therefore forbidden. Consider whether your use case fits into one of the other patterns described in this section. One simple solution is to use a plain pointer to a dynamically allocated object and never delete it (see last item). - 智能指针(
std::unique_ptr,std::shared_ptr):智能指针在析构期间执行清理,因此是被禁止的。考虑你的用例是否符合本节中描述的其他模式之一。一个简单的解决方案是使用指向动态分配对象的普通指针,并且永远不删除它(见最后一条)。 - Static variables of custom types: if you require static, constant data of
a type that you need to define yourself, give the type a trivial destructor
and a
constexprconstructor. - 自定义类型的静态变量:如果你需要自定义类型的静态常量数据,请为该类型提供一个平凡的析构函数和一个
constexpr构造函数。 - If all else fails, you can create an object dynamically and never delete
it by using a function-local static pointer or reference (e.g.,
static const auto& impl = *new T(args...);). - 如果其他方法都失败了,你可以通过使用函数局部的静态指针或引用(例如,
static const auto& impl = *new T(args...);)来动态创建对象,并且永远不删除它。
thread_local Variables 线程局部变量
thread_local variables that aren't declared inside a function
must be initialized with a true compile-time constant,
and this must be enforced by using the
constinit
attribute. Prefer
thread_local over other ways of defining thread-local data.
未在函数内部声明的 thread_local 变量必须使用真正的编译时常量进行初始化,并且必须使用 constinit 属性强制执行此操作。优先使用 thread_local 而不是其他定义线程局部数据的方法。
Variables can be declared with the
thread_local specifier:
可以使用 thread_local 说明符声明变量:
thread_local Foo foo = ...;
Such a variable is actually a collection of objects, so that when different
threads access it, they are actually accessing different objects.
thread_local variables are much like
static storage duration variables
in many respects. For instance, they can be declared at namespace scope,
inside functions, or as static class members, but not as ordinary class
members.
这样的变量实际上是对象的集合,因此当不同的线程访问它时,它们实际上是在访问不同的对象。thread_local 变量在许多方面非常像 静态存储期变量。例如,它们可以在命名空间作用域、函数内部或作为静态类成员声明,但不能作为普通类成员声明。
thread_local variable instances are initialized much like
static variables, except that they must be initialized separately for each
thread, rather than once at program startup. This means that
thread_local variables declared within a function are safe, but
other thread_local variables are subject to the same
initialization-order issues as static variables (and more besides).
thread_local 变量实例的初始化非常像静态变量,不同之处在于它们必须为每个线程单独初始化,而不是在程序启动时初始化一次。这意味着在函数内声明的 thread_local 变量是安全的,但其他 thread_local 变量受制于与静态变量相同的初始化顺序问题(以及其他问题)。
thread_local variables have a subtle destruction-order issue:
during thread shutdown, thread_local variables will be destroyed
in the opposite order of their initialization (as is generally true in C++).
If code triggered by the destructor of any thread_local variable
refers to any already-destroyed thread_local on that thread, we will
get a particularly hard to diagnose use-after-free.
thread_local 变量有一个微妙的销毁顺序问题:在线程关闭期间,thread_local 变量将按其初始化的相反顺序销毁(这在 C++ 中通常如此)。如果任何 thread_local 变量的析构函数触发的代码引用了该线程上任何已销毁的 thread_local,我们将遇到特别难以诊断的 use-after-free 问题。
- Thread-local data is inherently safe from races (because only one thread
can ordinarily access it), which makes
thread_localuseful for concurrent programming. - 线程局部数据本质上是免受竞争影响的(因为通常只有一个线程可以访问它),这使得
thread_local对并发编程很有用。 thread_localis the only standard-supported way of creating thread-local data.thread_local是唯一受标准支持的创建线程局部数据的方法。
- Accessing a
thread_localvariable may trigger execution of an unpredictable and uncontrollable amount of other code during thread-start or first use on a given thread. - 访问
thread_local变量可能会在线程启动或给定线程首次使用期间触发执行不可预测且不可控的大量其他代码。 thread_localvariables are effectively global variables, and have all the drawbacks of global variables other than lack of thread-safety.thread_local变量实际上是全局变量,并且具有全局变量的所有缺点,除了缺乏线程安全性。- The memory consumed by a
thread_localvariable scales with the number of running threads (in the worst case), which can be quite large in a program. thread_local变量消耗的内存随运行线程的数量(在最坏情况下)成比例增加,这在程序中可能会非常大。- Data members cannot be
thread_localunless they are alsostatic. - 数据成员不能是
thread_local,除非它们也是static。 - We may suffer from use-after-free bugs if
thread_localvariables have complex destructors. In particular, the destructor of any such variable must not call any code (transitively) that refers to any potentially-destroyedthread_local. This property is hard to enforce. - 如果
thread_local变量具有复杂的析构函数,我们可能会遇到 use-after-free 错误。特别是,任何此类变量的析构函数不得调用(传递地)任何引用任何可能已销毁的thread_local的代码。这个属性很难强制执行。 - Approaches for avoiding use-after-free in global/static contexts do not work for
thread_locals. Specifically, skipping destructors for globals and static variables is allowable because their lifetimes end at program shutdown. Thus, any "leak" is managed immediately by the OS cleaning up our memory and other resources. By contrast, skipping destructors forthread_localvariables leads to resource leaks proportional to the total number of threads that terminate during the lifetime of the program. - 避免在全局/静态上下文中出现 use-after-free 的方法不适用于
thread_local。具体来说,跳过全局变量和静态变量的析构函数是允许的,因为它们的生命周期在程序关闭时结束。因此,任何“泄漏”都由操作系统立即清理我们的内存和其他资源来管理。相比之下,跳过thread_local变量的析构函数会导致资源泄漏,其泄漏量与程序生命周期内终止的线程总数成正比。
thread_local variables at class or namespace scope must be
initialized with a true compile-time constant (i.e., they must have no
dynamic initialization). To enforce this, thread_local variables
at class or namespace scope must be annotated with
constinit
(or constexpr, but that should be rare):
类或命名空间作用域的 thread_local 变量必须使用真正的编译时常量进行初始化(即,它们必须没有动态初始化)。为了强制执行这一点,类或命名空间作用域的 thread_local 变量必须使用 constinit(或 constexpr,但这应该很少见)进行注释:
constinit thread_local Foo foo = ...;
thread_local variables inside a function have no initialization
concerns, but still risk use-after-free during thread exit. Note that you can use
a function-scope thread_local to simulate a class- or
namespace-scope thread_local by defining a function or
static method that exposes it:
函数内部的 thread_local 变量没有初始化问题,但在线程退出期间仍有 use-after-free 的风险。请注意,你可以通过定义一个暴露它的函数或静态方法,使用函数作用域的 thread_local 来模拟类或命名空间作用域的 thread_local:
Foo& MyThreadLocalFoo() {
thread_local Foo result = ComplicatedInitialization();
return result;
}
Note that thread_local variables will be destroyed whenever a thread exits.
If the destructor of any such variable refers to any other (potentially-destroyed)
thread_local we will suffer from hard to diagnose use-after-free bugs.
Prefer trivial types, or types that provably run no user-provided code at destruction to
minimize the potential of accessing any other thread_local.
请注意,只要线程退出,thread_local 变量就会被销毁。如果任何此类变量的析构函数引用任何其他(可能已销毁的)thread_local,我们将遇到难以诊断的 use-after-free 错误。优先使用平凡类型,或证明在销毁时不运行用户提供的代码的类型,以最大程度地减少访问任何其他 thread_local 的可能性。
thread_local should be preferred over other mechanisms for
defining thread-local data.
应优先使用 thread_local 而不是其他定义线程局部数据的机制。
Classes 类
Classes are the fundamental unit of code in C++. Naturally, we use them extensively. This section lists the main dos and don'ts you should follow when writing a class.
类是 C++ 中代码的基本单位。自然地,我们会广泛使用它们。本节列出了编写类时应遵循的主要注意事项。
Doing Work in Constructors 构造函数中的工作
Avoid virtual method calls in constructors, and avoid initialization that can fail if you can't signal an error.
避免在构造函数中调用虚方法,如果在无法发出错误信号的情况下初始化可能失败,也要避免这种初始化。
It is possible to perform arbitrary initialization in the body of the constructor.
可以在构造函数体中执行任意初始化。
- No need to worry about whether the class has been initialized or not.
- 无需担心类是否已初始化。
- Objects that are fully initialized by constructor call can
be
constand may also be easier to use with standard containers or algorithms. - 通过构造函数调用完全初始化的对象可以是
const,并且可能更容易与标准容器或算法一起使用。
- If the work calls virtual functions, these calls will not get dispatched to the subclass implementations. Future modification to your class can quietly introduce this problem even if your class is not currently subclassed, causing much confusion.
- 如果工作调用了虚函数,这些调用将不会分发到子类的实现。即使你的类目前没有子类,未来对类的修改也可能悄悄地引入这个问题,从而导致很多困惑。
- There is no easy way for constructors to signal errors, short of crashing the program (not always appropriate) or using exceptions (which are forbidden).
- 除了使程序崩溃(并不总是合适)或使用异常(这是 被禁止的)之外,构造函数没有简单的方法来发出错误信号。
- If the work fails, we now have an object whose initialization
code failed, so it may be an unusual state requiring a
bool IsValid()state checking mechanism (or similar) which is easy to forget to call. - 如果工作失败,我们现在就有一个初始化代码失败的对象,因此它可能处于一种不寻常的状态,需要一个
bool IsValid()状态检查机制(或类似的机制),而这种机制很容易忘记调用。 - You cannot take the address of a constructor, so whatever work is done in the constructor cannot easily be handed off to, for example, another thread.
- 你不能获取构造函数的地址,因此构造函数中所做的任何工作都不能轻易地移交给例如另一个线程。
Constructors should never call virtual functions. If appropriate
for your code ,
terminating the program may be an appropriate error handling
response. Otherwise, consider a factory function
or Init() method as described in
TotW #42
.
Avoid Init() methods on objects with
no other states that affect which public methods may be called
(semi-constructed objects of this form are particularly hard to work
with correctly).
构造函数绝不应调用虚函数。如果适合你的代码,终止程序可能是一种合适的错误处理响应。否则,请考虑使用工厂函数或 TotW #42 中描述的 Init() 方法。避免在没有其他状态影响哪些公共方法可以被调用的对象上使用 Init() 方法(这种形式的半构造对象特别难以正确使用)。
Implicit Conversions 隐式转换
Do not define implicit conversions. Use the explicit
keyword for conversion operators and single-argument
constructors.
不要定义隐式转换。对转换运算符和单参数构造函数使用 explicit 关键字。
Implicit conversions allow an
object of one type (called the source type) to
be used where a different type (called the destination
type) is expected, such as when passing an
int argument to a function that takes a
double parameter.
隐式转换允许在需要不同类型(称为目标类型)的地方使用一种类型(称为源类型)的对象,例如将 int 参数传递给接受 double 参数的函数。
In addition to the implicit conversions defined by the language,
users can define their own, by adding appropriate members to the
class definition of the source or destination type. An implicit
conversion in the source type is defined by a type conversion operator
named after the destination type (e.g., operator
bool()). An implicit conversion in the destination
type is defined by a constructor that can take the source type as
its only argument (or only argument with no default value).
除了语言定义的隐式转换外,用户还可以通过向源类型或目标类型的类定义中添加适当的成员来定义自己的隐式转换。源类型中的隐式转换由以目标类型命名的类型转换运算符(例如 operator bool())定义。目标类型中的隐式转换由可以接受源类型作为其唯一参数(或唯一没有默认值的参数)的构造函数定义。
The explicit keyword can be applied to a constructor
or a conversion operator, to ensure that it can only be
used when the destination type is explicit at the point of use,
e.g., with a cast. This applies not only to implicit conversions, but to
list initialization syntax:
explicit 关键字可以应用于构造函数或转换运算符,以确保它只能在目标类型在代码中明确指定时使用,例如使用强制转换。这不仅适用于隐式转换,也适用于列表初始化语法:
class Foo {
explicit Foo(int x, double y);
...
};
void Func(Foo f);
Func({42, 3.14}); // Error
This kind of code isn't technically an implicit conversion, but the
language treats it as one as far as explicit is concerned.
从技术上讲,这种代码不是隐式转换,但就 explicit 而言,语言将其视为隐式转换。
- Implicit conversions can make a type more usable and expressive by eliminating the need to explicitly name a type when it's obvious.
- 隐式转换可以消除在类型显而易见时显式命名类型的需要,从而使类型更易用、更具表达力。
- Implicit conversions can be a simpler alternative to
overloading, such as when a single
function with a
string_viewparameter takes the place of separate overloads forstd::stringandconst char*. - 隐式转换可以是重载的更简单替代方案,例如,单个具有
string_view参数的函数可以替代std::string和const char*的单独重载。 - List initialization syntax is a concise and expressive way of initializing objects.
- 列表初始化语法是一种简洁且富有表现力的对象初始化方式。
- Implicit conversions can hide type-mismatch bugs, where the destination type does not match the user's expectation, or the user is unaware that any conversion will take place.
- 隐式转换可能会隐藏类型不匹配的错误,即目标类型不符合用户预期,或者用户不知道会发生任何转换。
- Implicit conversions can make code harder to read, particularly in the presence of overloading, by making it less obvious what code is actually getting called.
- 隐式转换会使代码更难阅读,尤其是在存在重载的情况下,因为很难看出实际上调用了什么代码。
- Constructors that take a single argument may accidentally be usable as implicit type conversions, even if they are not intended to do so.
- 接受单个参数的构造函数可能会意外地用作隐式类型转换,即使本意并非如此。
- When a single-argument constructor is not marked
explicit, there's no reliable way to tell whether it's intended to define an implicit conversion, or the author simply forgot to mark it. - 当单参数构造函数未标记为
explicit时,没有可靠的方法来判断它是旨在定义隐式转换,还是作者仅仅忘记了标记它。 - Implicit conversions can lead to call-site ambiguities, especially when there are bidirectional implicit conversions. This can be caused either by having two types that both provide an implicit conversion, or by a single type that has both an implicit constructor and an implicit type conversion operator.
- 隐式转换可能会导致调用点歧义,尤其是在存在双向隐式转换时。这可能是由于两种类型都提供隐式转换,或者单一类型既有隐式构造函数又有隐式类型转换运算符引起的。
- List initialization can suffer from the same problems if the destination type is implicit, particularly if the list has only a single element.
- 如果目标类型是隐式的,列表初始化也会遇到同样的问题,特别是如果列表只有一个元素。
Type conversion operators, and constructors that are
callable with a single argument, must be marked
explicit in the class definition. As an
exception, copy and move constructors should not be
explicit, since they do not perform type
conversion.
类型转换运算符和可以用单个参数调用的构造函数必须在类定义中标记为 explicit。作为例外,复制和移动构造函数不应是 explicit 的,因为它们不执行类型转换。
Implicit conversions can sometimes be necessary and appropriate for types that are designed to be interchangeable, for example when objects of two types are just different representations of the same underlying value. In that case, contact your project leads to request a waiver of this rule.
对于设计为可互换的类型,隐式转换有时是必要且适当的,例如当两种类型的对象只是同一底层值的不同表示形式时。在这种情况下,请联系你的项目负责人以申请豁免此规则。
Constructors that cannot be called with a single argument
may omit explicit. Constructors that
take a single std::initializer_list parameter should
also omit explicit, in order to support copy-initialization
(e.g., MyType m = {1, 2};).
无法用单个参数调用的构造函数可以省略 explicit。接受单个 std::initializer_list 参数的构造函数也应省略 explicit,以支持复制初始化(例如 MyType m = {1, 2};)。
Copyable and Movable Types 可拷贝和可移动类型
A class's public API must make clear whether the class is copyable, move-only, or neither copyable nor movable. Support copying and/or moving if these operations are clear and meaningful for your type.
类的公共 API 必须明确该类是可拷贝的、仅可移动的,还是既不可拷贝也不可移动的。如果拷贝和/或移动操作对你的类型清晰且有意义,请支持这些操作。
A movable type is one that can be initialized and assigned from temporaries.
可移动类型是指可以从临时对象初始化和赋值的类型。
A copyable type is one that can be initialized or assigned from
any other object of the same type (so is also movable by definition), with the
stipulation that the value of the source does not change.
std::unique_ptr<int> is an example of a movable but not
copyable type (since the value of the source
std::unique_ptr<int> must be modified during assignment to
the destination). int and std::string are examples of
movable types that are also copyable. (For int, the move and copy
operations are the same; for std::string, there exists a move operation
that is less expensive than a copy.)
可拷贝类型是指可以从同一类型的任何其他对象初始化或赋值的类型(因此根据定义也是可移动的),并规定源对象的值不会改变。std::unique_ptr<int> 是一个可移动但不可拷贝类型的示例(因为在赋值给目标时,源 std::unique_ptr<int> 的值必须被修改)。int 和 std::string 是既可移动又可拷贝类型的示例。(对于 int,移动和拷贝操作是相同的;对于 std::string,存在比拷贝代价更小的移动操作。)
For user-defined types, the copy behavior is defined by the copy constructor and the copy-assignment operator. Move behavior is defined by the move constructor and the move-assignment operator, if they exist, or by the copy constructor and the copy-assignment operator otherwise.
对于用户定义的类型,拷贝行为由拷贝构造函数和拷贝赋值运算符定义。移动行为由移动构造函数和移动赋值运算符(如果存在)定义,否则由拷贝构造函数和拷贝赋值运算符定义。
The copy/move constructors can be implicitly invoked by the compiler in some situations, e.g., when passing objects by value.
在某些情况下(例如按值传递对象时),编译器可以隐式调用拷贝/移动构造函数。
Objects of copyable and movable types can be passed and returned by value, which makes APIs simpler, safer, and more general. Unlike when passing objects by pointer or reference, there's no risk of confusion over ownership, lifetime, mutability, and similar issues, and no need to specify them in the contract. It also prevents non-local interactions between the client and the implementation, which makes them easier to understand, maintain, and optimize by the compiler. Further, such objects can be used with generic APIs that require pass-by-value, such as most containers, and they allow for additional flexibility in e.g., type composition.
可拷贝和可移动类型的对象可以按值传递和返回,这使得 API 更简单、更安全且更通用。与按指针或引用传递对象不同,不存在所有权、生命周期、可变性等问题的混淆风险,也不需要在契约中指定它们。它还可以防止客户端和实现之间的非局部交互,这使得它们更容易理解、维护,并且更容易被编译器优化。此外,此类对象可以与需要按值传递的通用 API(如大多数容器)一起使用,并且它们允许在例如类型组合方面具有额外的灵活性。
Copy/move constructors and assignment operators are usually
easier to define correctly than alternatives
like Clone(), CopyFrom() or Swap(),
because they can be generated by the compiler, either implicitly or
with = default. They are concise, and ensure
that all data members are copied. Copy and move
constructors are also generally more efficient, because they don't
require heap allocation or separate initialization and assignment
steps, and they're eligible for optimizations such as
copy elision.
Move operations allow the implicit and efficient transfer of resources out of rvalue objects. This allows a plainer coding style in some cases.
Some types do not need to be copyable, and providing copy
operations for such types can be confusing, nonsensical, or outright
incorrect. Types representing singleton objects (Registerer),
objects tied to a specific scope (Cleanup), or closely coupled to
object identity (Mutex) cannot be copied meaningfully.
Copy operations for base class types that are to be used
polymorphically are hazardous, because use of them can lead to
object slicing.
Defaulted or carelessly-implemented copy operations can be incorrect, and the
resulting bugs can be confusing and difficult to diagnose.
Copy constructors are invoked implicitly, which makes the invocation easy to miss. This may cause confusion for programmers used to languages where pass-by-reference is conventional or mandatory. It may also encourage excessive copying, which can cause performance problems.
拷贝构造函数是隐式调用的,这使得调用很容易被忽略。这可能会让习惯于按引用传递是常规或强制性做法的语言的程序员感到困惑。这也可能鼓励过多的拷贝,从而导致性能问题。
Every class's public interface must make clear which copy and move
operations the class supports. This should usually take the form of explicitly
declaring and/or deleting the appropriate operations in the public
section of the declaration.
每个类的公共接口必须明确该类支持哪些拷贝和移动操作。这通常应采用在声明的 public 部分显式声明和/或删除适当操作的形式。
Specifically, a copyable class should explicitly declare the copy operations, a move-only class should explicitly declare the move operations, and a non-copyable/movable class should explicitly delete the copy operations. A copyable class may also declare move operations in order to support efficient moves. Explicitly declaring or deleting all four copy/move operations is permitted, but not required. If you provide a copy or move assignment operator, you must also provide the corresponding constructor.
具体来说,可拷贝类应显式声明拷贝操作,仅可移动类应显式声明移动操作,不可拷贝/移动类应显式删除拷贝操作。可拷贝类也可以声明移动操作以支持高效移动。允许但不强制要求显式声明或删除所有四个拷贝/移动操作。如果你提供了拷贝或移动赋值运算符,你也必须提供相应的构造函数。
class Copyable {
public:
Copyable(const Copyable& other) = default;
Copyable& operator=(const Copyable& other) = default;
// The implicit move operations are suppressed by the declarations above.
// You may explicitly declare move operations to support efficient moves.
};
class MoveOnly {
public:
MoveOnly(MoveOnly&& other) = default;
MoveOnly& operator=(MoveOnly&& other) = default;
// The copy operations are implicitly deleted, but you can
// spell that out explicitly if you want:
MoveOnly(const MoveOnly&) = delete;
MoveOnly& operator=(const MoveOnly&) = delete;
};
class NotCopyableOrMovable {
public:
// Not copyable or movable
NotCopyableOrMovable(const NotCopyableOrMovable&) = delete;
NotCopyableOrMovable& operator=(const NotCopyableOrMovable&)
= delete;
// The move operations are implicitly disabled, but you can
// spell that out explicitly if you want:
NotCopyableOrMovable(NotCopyableOrMovable&&) = delete;
NotCopyableOrMovable& operator=(NotCopyableOrMovable&&)
= delete;
};
These declarations/deletions can be omitted only if they are obvious:
只有当这些声明/删除显而易见时,才可以省略:
- If the class has no
privatesection, like a struct or an interface-only base class, then the copyability/movability can be determined by the copyability/movability of any public data members. - 如果类没有
private部分,例如 struct 或仅有接口的基类,那么其可拷贝性/可移动性可以由任何公共数据成员的可拷贝性/可移动性决定。 - If a base class clearly isn't copyable or movable, derived classes naturally won't be either. An interface-only base class that leaves these operations implicit is not sufficient to make concrete subclasses clear.
- 如果基类显然不可拷贝或不可移动,派生类自然也不会是。仅保留这些操作为隐式的接口基类不足以明确具体的子类。
- Note that if you explicitly declare or delete either the constructor or assignment operation for copy, the other copy operation is not obvious and must be declared or deleted. Likewise for move operations.
- 请注意,如果你显式声明或删除拷贝构造函数或拷贝赋值操作符中的一个,另一个拷贝操作就不再显而易见,必须显式声明或删除。移动操作同理。
A type should not be copyable/movable if the meaning of copying/moving is unclear to a casual user, or if it incurs unexpected costs. Move operations for copyable types are strictly a performance optimization and are a potential source of bugs and complexity, so avoid defining them unless they are significantly more efficient than the corresponding copy operations. If your type provides copy operations, it is recommended that you design your class so that the default implementation of those operations is correct. Remember to review the correctness of any defaulted operations as you would any other code.
如果拷贝/移动的含义对普通用户来说不清楚,或者会产生意想不到的成本,那么该类型就不应该是可拷贝/可移动的。对于可拷贝类型,移动操作严格来说是一种性能优化,也是潜在的错误和复杂性来源,因此除非它们比相应的拷贝操作效率高得多,否则应避免定义它们。如果你的类型提供拷贝操作,建议你设计你的类,使得这些操作的默认实现是正确的。请记住,像审查任何其他代码一样审查任何默认操作的正确性。
To eliminate the risk of slicing, prefer to make base classes abstract, by making their constructors protected, by declaring their destructors protected, or by giving them one or more pure virtual member functions. Prefer to avoid deriving from concrete classes.
为了消除切片的风险,最好通过将其构造函数设为 protected,将其析构函数声明为 protected,或赋予其一个或多个纯虚成员函数,使基类成为抽象类。尽量避免从具体类派生。
Structs vs. Classes 结构体 vs. 类
Use a struct only for passive objects that
carry data; everything else is a class.
仅将 struct 用于承载数据的被动对象;其他所有情况都使用 class。
The struct and class
keywords behave almost identically in C++. We add our own
semantic meanings to each keyword, so you should use the
appropriate keyword for the data-type you're
defining.
struct 和 class 关键字在 C++ 中的行为几乎相同。我们为每个关键字赋予了自己的语义含义,因此你应该为你定义的类型使用适当的关键字。
structs should be used for passive objects that carry
data, and may have associated constants. All fields must be public. The
struct type itself must not have invariants that imply relationships between
different fields, since direct user access to those fields may
break those invariants, but users of a struct may have requirements and
guarantees on particular uses of it. Constructors, destructors, and helper
methods may be present; however, these methods must not require or enforce
any invariants.
struct 应被用于承载数据的被动对象,并可能包含关联的常量。所有字段必须是 public 的。Struct 类型本身不得具有暗示不同字段之间关系的不变式,因为用户直接访问这些字段可能会破坏这些不变式,但 struct 的用户可能对其特定用途有要求和保证。可以存在构造函数、析构函数和辅助方法;但是,这些方法不得要求或强制执行任何不变式。
If more functionality or invariants are required, or struct has wide visibility and expected to
evolve, then a class is more appropriate. If in doubt, make it a class.
如果需要更多功能或不变式,或者 struct 具有广泛的可见性并预期会演变,那么 class 更合适。如果有疑问,请将其设为 class。
For consistency with STL, you can use
struct instead of class for
stateless types, such as traits,
template metafunctions,
and some functors.
为了与 STL 保持一致,对于无状态类型(例如 traits、模板元函数 和某些仿函数),可以使用 struct 代替 class。
Note that member variables in structs and classes have different naming rules.
请注意,struct 和 class 中的成员变量具有 不同的命名规则。
Structs vs. Pairs and Tuples 结构体 vs. Pairs 和 Tuples
Prefer to use a struct instead of a pair or a
tuple whenever the elements can have meaningful names.
只要元素可以有有意义的名称,就优先使用 struct 而不是 pair 或 tuple。
While using pairs and tuples can avoid the need to define a custom type,
potentially saving work when writing code, a meaningful field
name will almost always be much clearer when reading code than
.first, .second, or std::get<X>.
While C++14's introduction of std::get<Type> to access a
tuple element by type rather than index (when the type is unique) can
sometimes partially mitigate this, a field name is usually substantially
clearer and more informative than a type.
虽然使用 pair 和 tuple 可以避免定义自定义类型的需要,从而可能在编写代码时节省工作量,但在阅读代码时,有意义的字段名称几乎总是比 .first、.second 或 std::get<X> 清晰得多。虽然 C++14 引入的 std::get<Type> 允许按类型而不是索引访问 tuple 元素(当类型唯一时),有时可以部分缓解这个问题,但字段名称通常比类型更清晰、更具信息量。
Pairs and tuples may be appropriate in generic code where there are not specific meanings for the elements of the pair or tuple. Their use may also be required in order to interoperate with existing code or APIs.
在 pair 或 tuple 的元素没有特定含义的通用代码中,pair 和 tuple 可能是合适的。为了与现有代码或 API 进行互操作,也可能需要使用它们。
Inheritance 继承
Composition is often more appropriate than inheritance.
When using inheritance, make it public.
组合通常比继承更合适。使用继承时,请将其设为 public。
When a sub-class inherits from a base class, it includes the definitions of all the data and operations that the base class defines. "Interface inheritance" is inheritance from a pure abstract base class (one with no state or defined methods); all other inheritance is "implementation inheritance".
当子类继承基类时,它包含基类定义的所有数据和操作的定义。“接口继承”是指从纯抽象基类(没有状态或已定义方法的类)继承;所有其他继承都是“实现继承”。
Implementation inheritance reduces code size by re-using the base class code as it specializes an existing type. Because inheritance is a compile-time declaration, you and the compiler can understand the operation and detect errors. Interface inheritance can be used to programmatically enforce that a class expose a particular API. Again, the compiler can detect errors, in this case, when a class does not define a necessary method of the API.
实现继承通过重用基类代码来减小代码大小,因为它特化了现有类型。因为继承是编译时声明,你和编译器都可以理解操作并检测错误。接口继承可用于以编程方式强制类公开特定 API。同样,编译器可以在这种情况下检测错误,即当类未定义 API 的必要方法时。
For implementation inheritance, because the code implementing a sub-class is spread between the base and the sub-class, it can be more difficult to understand an implementation. The sub-class cannot override functions that are not virtual, so the sub-class cannot change implementation.
对于实现继承,由于实现子类的代码分散在基类和子类之间,因此可能更难理解实现。子类无法覆盖非虚函数,因此子类无法更改实现。
Multiple inheritance is especially problematic, because it often imposes a higher performance overhead (in fact, the performance drop from single inheritance to multiple inheritance can often be greater than the performance drop from ordinary to virtual dispatch), and because it risks leading to "diamond" inheritance patterns, which are prone to ambiguity, confusion, and outright bugs.
多重继承特别有问题,因为它通常会带来更高的性能开销(事实上,从单继承到多重继承的性能下降通常比从普通调度到虚调度的性能下降更大),而且因为它有可能导致“菱形”继承模式,这种模式容易产生歧义、困惑甚至错误。
All inheritance should be public. If you
want to do private inheritance, you should be including
an instance of the base class as a member instead. You may use
final on classes when you don't intend to support using
them as base classes.
所有继承都应该是 public。如果你想进行私有继承,你应该改为将基类的实例包含为成员。当你不想支持将类用作基类时,可以在类上使用 final。
Do not overuse implementation inheritance. Composition
is often more appropriate. Try to restrict use of
inheritance to the "is-a" case: Bar
subclasses Foo if it can reasonably be said
that Bar "is a kind of"
Foo.
不要过度使用实现继承。组合通常更合适。尝试将继承的使用限制在 "is-a" 情况下:如果可以说 Bar "是一种" Foo,则 Bar 继承 Foo。
Limit the use of protected to those
member functions that might need to be accessed from
subclasses. Note that data
members should be private.
限制 protected 的使用,仅用于那些可能需要从子类访问的成员函数。请注意 数据成员应该是 private。
Explicitly annotate overrides of virtual functions or virtual
destructors with exactly one of an override or (less
frequently) final specifier. Do not
use virtual when declaring an override.
Rationale: A function or destructor marked
override or final that is
not an override of a base class virtual function will
not compile, and this helps catch common errors. The
specifiers serve as documentation; if no specifier is
present, the reader has to check all ancestors of the
class in question to determine if the function or
destructor is virtual or not.
使用 override 或(较少见)final 说明符之一明确注释虚函数或虚析构函数的覆盖。在声明覆盖时不要使用 virtual。理由:标记为 override 或 final 但不是基类虚函数覆盖的函数或析构函数将无法编译,这有助于捕获常见错误。说明符充当文档;如果没有说明符,读者必须检查相关类的所有祖先,以确定函数或析构函数是否为虚函数。
Multiple inheritance is permitted, but multiple implementation inheritance is strongly discouraged.
允许多重继承,但强烈不鼓励多重实现继承。
Operator Overloading 运算符重载
Overload operators judiciously. Do not use user-defined literals.
谨慎地重载运算符。不要使用用户定义的字面量。
C++ permits user code to
declare
overloaded versions of the built-in operators using the
operator keyword, so long as one of the parameters
is a user-defined type. The operator keyword also
permits user code to define new kinds of literals using
operator"", and to define type-conversion functions
such as operator bool().
C++ 允许用户代码使用 operator 关键字 声明内置运算符的重载版本,只要其中一个参数是用户定义的类型。operator 关键字还允许用户代码使用 operator"" 定义新型字面量,并定义类型转换函数,例如 operator bool()。
Operator overloading can make code more concise and
intuitive by enabling user-defined types to behave the same
as built-in types. Overloaded operators are the idiomatic names
for certain operations (e.g., ==, <,
=, and <<), and adhering to
those conventions can make user-defined types more readable
and enable them to interoperate with libraries that expect
those names.
运算符重载可以使代码更简洁、直观,因为它使用户定义的类型能够像内置类型一样行事。重载运算符是某些操作的习惯名称(例如 ==、<、= 和 <<),遵循这些约定可以使用户定义的类型更具可读性,并使其能够与期望这些名称的库进行互操作。
User-defined literals are a very concise notation for creating objects of user-defined types.
用户定义的字面量是创建用户定义类型对象的一种非常简洁的符号。
- Providing a correct, consistent, and unsurprising set of operator overloads requires some care, and failure to do so can lead to confusion and bugs.
- 提供一套正确、一致且令人不意外的运算符重载需要一些小心,如果做不到这一点,可能会导致困惑和错误。
- Overuse of operators can lead to obfuscated code, particularly if the overloaded operator's semantics don't follow convention.
- 过度使用运算符可能会导致代码晦涩难懂,特别是如果重载运算符的语义不遵循约定时。
- The hazards of function overloading apply just as much to operator overloading, if not more so.
- 函数重载的危害同样适用于运算符重载,甚至可能更严重。
- Operator overloads can fool our intuition into thinking that expensive operations are cheap, built-in operations.
- 运算符重载可能会欺骗我们的直觉,让我们误以为昂贵的操作是廉价的内置操作。
- Finding the call sites for overloaded operators may require a search tool that's aware of C++ syntax, rather than, e.g., grep.
- 查找重载运算符的调用点可能需要一个了解 C++ 语法的搜索工具,而不是像 grep 这样的工具。
- If you get the argument type of an overloaded operator
wrong, you may get a different overload rather than a
compiler error. For example,
foo < barmay do one thing, while&foo < &bardoes something totally different. - 如果你弄错了重载运算符的参数类型,你可能会得到不同的重载,而不是编译器错误。例如,
foo < bar可能会做一件事,而&foo < &bar会做完全不同的事情。 - Certain operator overloads are inherently hazardous.
Overloading unary
&can cause the same code to have different meanings depending on whether the overload declaration is visible. Overloads of&&,||, and,(comma) cannot match the evaluation-order semantics of the built-in operators. - 某些运算符重载本质上是危险的。重载一元
&可能会导致同一代码具有不同的含义,具体取决于重载声明是否可见。&&、||和,(逗号)的重载无法匹配内置运算符的求值顺序语义。 - Operators are often defined outside the class, so there's a risk of different files introducing different definitions of the same operator. If both definitions are linked into the same binary, this results in undefined behavior, which can manifest as subtle run-time bugs.
- 运算符通常在类外部定义,因此存在不同文件引入同一运算符不同定义的风险。如果两个定义都链接到同一个二进制文件中,这将导致未定义行为,可能表现为微妙的运行时错误。
- User-defined literals (UDLs) allow the creation of new
syntactic forms that are unfamiliar even to experienced C++
programmers, such as
"Hello World"svas a shorthand forstd::string_view("Hello World"). Existing notations are clearer, though less terse. - 用户定义字面量 (UDL) 允许创建连经验丰富的 C++ 程序员都不熟悉的新语法形式,例如用
"Hello World"sv作为std::string_view("Hello World")的简写。现有的符号虽然不那么简洁,但更清晰。 - Because they can't be namespace-qualified, uses of UDLs also require use of either using-directives (which we ban) or using-declarations (which we ban in header files except when the imported names are part of the interface exposed by the header file in question). Given that header files would have to avoid UDL suffixes, we prefer to avoid having conventions for literals differ between header files and source files.
- 由于 UDL 不能被命名空间限定,因此使用 UDL 还需要使用 using 指令(我们禁止)或 using 声明(我们在头文件中禁止,除非导入的名称是该头文件公开接口的一部分)。鉴于头文件必须避免 UDL 后缀,我们宁愿避免头文件和源文件之间的字面量约定不同。
Define overloaded operators only if their meaning is
obvious, unsurprising, and consistent with the corresponding
built-in operators. For example, use | as a
bitwise- or logical-or, not as a shell-style pipe.
只有当重载运算符的含义明显、不令人意外且与相应的内置运算符一致时,才定义重载运算符。例如,将 | 用作按位或或逻辑或,而不是 shell 风格的管道。
Define operators only on your own types. More precisely,
define them in the same headers, .cc files, and namespaces
as the types they operate on. That way, the operators are available
wherever the type is, minimizing the risk of multiple
definitions. If possible, avoid defining operators as templates,
because they must satisfy this rule for any possible template
arguments. If you define an operator, also define
any related operators that make sense, and make sure they
are defined consistently.
仅在你自己的类型上定义运算符。更准确地说,在与其操作的类型相同的头文件、.cc 文件和命名空间中定义它们。这样,只要类型可用,运算符就可用,从而最大限度地减少多重定义的风险。如果可能,避免将运算符定义为模板,因为它们必须满足任何可能的模板参数的这条规则。如果你定义了一个运算符,请同时定义任何有意义的相关运算符,并确保它们的定义是一致的。
Prefer to define non-modifying binary operators as
non-member functions. If a binary operator is defined as a
class member, implicit conversions will apply to the
right-hand argument, but not the left-hand one. It will
confuse your users if a + b compiles but
b + a doesn't.
优先将非修改性二元运算符定义为非成员函数。如果二元运算符定义为类成员,则隐式转换将应用于右侧参数,但不应用于左侧参数。如果 a + b 编译通过但 b + a 编译失败,这会让你的用户感到困惑。
For a type T whose values can be compared for
equality, define a non-member operator== and document when
two values of type T are considered equal.
If there is a single obvious notion of when a value t1
of type T is less than another such value t2 then
you may also define operator<=>, which should be
consistent with operator==.
Prefer not to overload the other comparison and ordering operators.
对于值可以比较相等的类型 T,定义一个非成员 operator== 并记录何时认为两个类型为 T 的值相等。如果对于类型 T 的值 t1 何时小于另一个此类值 t2 有一个明显的单一概念,那么你也可以定义 operator<=>,它应该与 operator== 一致。最好不要重载其他比较和排序运算符。
Don't go out of your way to avoid defining operator
overloads. For example, prefer to define ==,
=, and <<, rather than
Equals(), CopyFrom(), and
PrintTo(). Conversely, don't define
operator overloads just because other libraries expect
them. For example, if your type doesn't have a natural
ordering, but you want to store it in a std::set,
use a custom comparator rather than overloading
<.
不要刻意避免定义运算符重载。例如,优先定义 ==、= 和 <<,而不是 Equals()、CopyFrom() 和 PrintTo()。相反,不要仅仅因为其他库期望它们就定义运算符重载。例如,如果你的类型没有自然排序,但你想将其存储在 std::set 中,请使用自定义比较器,而不是重载 <。
Do not overload &&, ||,
, (comma), or unary &. Do not overload
operator"", i.e., do not introduce user-defined
literals. Do not use any such literals provided by others
(including the standard library).
Type conversion operators are covered in the section on
implicit conversions.
The = operator is covered in the section on
copy constructors. Overloading
<< for use with streams is covered in the
section on streams. See also the rules on
function overloading, which
apply to operator overloading as well.
Access Control
Make classes' data members private, unless they are
constants. This simplifies reasoning about invariants, at the cost
of some easy boilerplate in the form of accessors (usually const) if necessary.
For technical
reasons, we allow data members of a test fixture class defined in a .cc file to
be protected when using
Google
Test.
If a test fixture class is defined outside of the .cc file it is used in, for example
in a .h file, make data members private.
Declaration Order 声明顺序
Group similar declarations together, placing public parts
earlier.
将相似的声明分组在一起,将 public 部分放在前面。
A class definition should usually start with a
public: section, followed by
protected:, then private:. Omit
sections that would be empty.
类定义通常应以 public: 部分开始,后跟 protected:,然后是 private:。省略空的部分。
Within each section, prefer grouping similar kinds of declarations together, and prefer the following order:
在每个部分中,最好将相似类型的声明分组在一起,并首选以下顺序:
- Types and type aliases (
typedef,using,enum, nested structs and classes, andfriendtypes) - 类型和类型别名(
typedef、using、enum、嵌套结构体和类以及friend类型) - (Optionally, for structs only) non-
staticdata members - (可选,仅适用于结构体)非
static数据成员 - Static constants
- 静态常量
- Factory functions
- 工厂函数
- Constructors and assignment operators
- 构造函数和赋值运算符
- Destructor
- 析构函数
-
All other functions (
staticand non-staticmember functions, andfriendfunctions) - 所有其他函数(
static和非static成员函数,以及friend函数) - All other data members (static and non-static)
- 所有其他数据成员(静态和非静态)
Do not put large method definitions inline in the class definition. Usually, only trivial or performance-critical, and very short, methods may be defined inline. See Defining Functions in Header Files for more details.
不要在类定义中内联放置大型方法定义。通常,只有简单的或性能关键且非常短的方法可以在内联定义。有关更多详细信息,请参阅 在头文件中定义函数。
Functions 函数
Inputs and Outputs 输入和输出
The output of a C++ function is naturally provided via a return value and sometimes via output parameters (or in/out parameters).
C++ 函数的输出自然通过返回值提供,有时通过输出参数(或输入/输出参数)提供。
Prefer using return values over output parameters: they improve readability, and often provide the same or better performance. See TotW #176.
优先使用返回值而不是输出参数:它们提高了可读性,并且通常提供相同或更好的性能。参见 TotW #176。
Prefer to return by value or, failing that, return by reference. Avoid returning a raw pointer unless it can be null.
优先按值返回,如果不行,则按引用返回。避免返回原始指针,除非它可以为 null。
Parameters are either inputs to the function, outputs from the
function, or both. Non-optional input parameters should usually be values
or const references, while non-optional output and
input/output parameters should usually be references (which cannot be null).
Generally, use std::optional to represent optional by-value
inputs, and use a const pointer when the non-optional form would
have used a reference. Use non-const pointers to represent
optional outputs and optional input/output parameters.
参数可以是函数的输入、函数的输出,或两者兼而有之。非可选输入参数通常应该是值或 const 引用,而非可选输出和输入/输出参数通常应该是引用(不能为 null)。通常,使用 std::optional 来表示可选的按值输入,当非可选形式使用引用时,使用 const 指针。使用非 const 指针来表示可选输出和可选输入/输出参数。
Avoid defining functions that require a reference parameter to outlive the call. In some cases reference parameters can bind to temporaries, leading to lifetime bugs. Instead, find a way to eliminate the lifetime requirement (for example, by copying the parameter), or pass retained parameters by pointer and document the lifetime and non-null requirements. See TotW 116 for more.
避免定义需要引用参数在调用后仍然存活的函数。在某些情况下,引用参数可以绑定到临时对象,导致生命周期错误。相反,找到一种消除生命周期要求的方法(例如,通过复制参数),或者通过指针传递保留参数,并记录生命周期和非空要求。更多信息请参见 TotW 116。
When ordering function parameters, put all input-only parameters before any output parameters. In particular, do not add new parameters to the end of the function just because they are new; place new input-only parameters before the output parameters. This is not a hard-and-fast rule. Parameters that are both input and output muddy the waters, and, as always, consistency with related functions may require you to bend the rule. Variadic functions may also require unusual parameter ordering.
在对函数参数进行排序时,将所有仅输入参数放在任何输出参数之前。特别是,不要仅仅因为它们是新的就将新参数添加到函数的末尾;将新的仅输入参数放在输出参数之前。这不是一条硬性规定。既是输入又是输出的参数会使情况变得复杂,而且一如既往,与相关函数的一致性可能需要你变通这条规则。变参函数也可能需要不寻常的参数排序。
Write Short Functions 编写短函数
Prefer small and focused functions.
倾向于编写小而专注的函数。
We recognize that long functions are sometimes appropriate, so no hard limit is placed on functions length. If a function exceeds about 40 lines, think about whether it can be broken up without harming the structure of the program.
我们认识到长函数有时是合适的,因此对函数长度没有硬性限制。如果一个函数超过约 40 行,请考虑是否可以在不损害程序结构的情况下将其拆分。
Even if your long function works perfectly now, someone modifying it in a few months may add new behavior. This could result in bugs that are hard to find. Keeping your functions short and simple makes it easier for other people to read and modify your code. Small functions are also easier to test.
即使你的长函数现在工作得很完美,几个月后修改它的人可能会添加新的行为。这可能会导致难以发现的错误。保持函数简短和简单,使其他人更容易阅读和修改你的代码。小函数也更容易测试。
You could find long and complicated functions when working with some code. Do not be intimidated by modifying existing code: if working with such a function proves to be difficult, you find that errors are hard to debug, or you want to use a piece of it in several different contexts, consider breaking up the function into smaller and more manageable pieces.
你在处理某些代码时可能会发现冗长而复杂的函数。不要被修改现有代码吓倒:如果处理这样的函数证明很困难,或者你发现错误难以调试,或者你想在几个不同的上下文中使用它的一部分,请考虑将该函数拆分为更小、更易于管理的部分。
Function Overloading 函数重载
Use overloaded functions (including constructors) only if a reader looking at a call site can get a good idea of what is happening without having to first figure out exactly which overload is being called.
仅当读者在查看调用点时,无需首先弄清楚到底调用了哪个重载就能很好地了解发生了什么时,才使用重载函数(包括构造函数)。
You may write a function that takes a const
std::string& and overload it with another that
takes const char*. However, in this case consider
std::string_view
instead.
你可以编写一个接受 const std::string& 的函数,并用另一个接受 const char* 的函数重载它。但是,在这种情况下,请考虑改用 std::string_view。
class MyClass {
public:
void Analyze(const std::string& text);
void Analyze(const char* text, size_t textlen);
};
Overloading can make code more intuitive by allowing an identically-named function to take different arguments. It may be necessary for templatized code, and it can be convenient for Visitors.
重载可以通过允许同名函数接受不同的参数使代码更直观。对于模板化代码,它可能是必要的,并且对于 Visitor 模式来说可能很方便。
Overloading based on const or ref qualification
may make utility code more usable, more efficient, or both.
See TotW #148 for more.
基于 const 或引用限定符的重载可以使实用代码更易用、更高效,或两者兼而有之。更多信息请参见 TotW #148。
If a function is overloaded by the argument types alone, a reader may have to understand C++'s complex matching rules in order to tell what's going on. Also many people are confused by the semantics of inheritance if a derived class overrides only some of the variants of a function.
如果函数仅通过参数类型重载,读者可能必须了解 C++ 复杂的匹配规则才能知道发生了什么。此外,如果派生类仅覆盖函数的某些变体,许多人会对继承的语义感到困惑。
You may overload a function when there are no semantic differences between variants. These overloads may vary in types, qualifiers, or argument count. However, a reader of such a call must not need to know which member of the overload set is chosen, only that something from the set is being called.
当变体之间没有语义差异时,你可以重载函数。这些重载可以在类型、限定符或参数数量上有所不同。但是,此类调用的读者必须不需要知道选择了重载集中的哪个成员,只需要知道调用了该集合中的 某个 东西。
To reflect this unified design, prefer a single, comprehensive "umbrella" comment that documents the entire overload set and is placed before the first declaration.
为了反映这种统一的设计,最好使用一个单一的、全面的“伞形”注释来记录整个重载集,并将其放在第一个声明之前。
Where a reader might have difficulty connecting the umbrella comment to a specific overload, it's okay to have a comment for specific overloads.
如果读者可能难以将伞形注释与特定重载联系起来,那么为特定重载添加注释是可以的。
Default Arguments 默认参数
Default arguments are allowed on non-virtual functions when the default is guaranteed to always have the same value. Follow the same restrictions as for function overloading, and prefer overloaded functions if the readability gained with default arguments doesn't outweigh the downsides below.
当默认值保证始终具有相同值时,允许在非虚函数上使用默认参数。遵循与 函数重载 相同的限制,如果默认参数获得的可读性不超过下面的缺点,则优先使用重载函数。
Often you have a function that uses default values, but occasionally you want to override the defaults. Default parameters allow an easy way to do this without having to define many functions for the rare exceptions. Compared to overloading the function, default arguments have a cleaner syntax, with less boilerplate and a clearer distinction between 'required' and 'optional' arguments.
通常你有一个使用默认值的函数,但偶尔你想覆盖默认值。默认参数允许一种简单的方法来做到这一点,而不必为罕见的例外定义许多函数。与重载函数相比,默认参数具有更清晰的语法,样板代码更少,并且“必需”和“可选”参数之间的区别更清晰。
Defaulted arguments are another way to achieve the semantics of overloaded functions, so all the reasons not to overload functions apply.
默认参数是实现重载函数语义的另一种方式,因此所有 不重载函数的原因 都适用。
The defaults for arguments in a virtual function call are determined by the static type of the target object, and there's no guarantee that all overrides of a given function declare the same defaults.
虚函数调用中参数的默认值由目标对象的静态类型决定,并且不能保证给定函数的所有覆盖都声明相同的默认值。
Default parameters are re-evaluated at each call site, which can bloat the generated code. Readers may also expect the default's value to be fixed at the declaration instead of varying at each call.
默认参数在每个调用点重新求值,这可能会使生成的代码膨胀。读者也可能期望默认值在声明时固定,而不是在每次调用时变化。
Function pointers are confusing in the presence of default arguments, since the function signature often doesn't match the call signature. Adding function overloads avoids these problems.
在存在默认参数的情况下,函数指针会令人困惑,因为函数签名通常与调用签名不匹配。添加函数重载可以避免这些问题。
Default arguments are banned on virtual functions, where
they don't work properly, and in cases where the specified
default might not evaluate to the same value depending on
when it was evaluated. (For example, don't write void
f(int n = counter++);.)
在虚函数上禁止使用默认参数,因为它们无法正常工作,并且在指定的默认值可能因求值时间不同而求值为不同值的情况下也是如此。(例如,不要写 void f(int n = counter++);。)
In some other cases, default arguments can improve the readability of their function declarations enough to overcome the downsides above, so they are allowed. When in doubt, use overloads.
在其他一些情况下,默认参数可以充分提高其函数声明的可读性,从而克服上述缺点,因此是允许的。如有疑问,请使用重载。
Trailing Return Type Syntax 尾置返回类型语法
Use trailing return types only where using the ordinary syntax (leading return types) is impractical or much less readable.
仅在普通语法(前置返回类型)不切实际或可读性差得多的情况下使用尾置返回类型。
C++ allows two different forms of function declarations. In the older form, the return type appears before the function name. For example:
C++ 允许两种不同形式的函数声明。在旧形式中,返回类型出现在函数名之前。例如:
int Foo(int x);
The newer form uses the auto
keyword before the function name and a trailing return type after
the argument list. For example, the declaration above could
equivalently be written:
新形式在函数名之前使用 auto 关键字,并在参数列表之后使用尾置返回类型。例如,上面的声明可以等效地写成:
auto Foo(int x) -> int;
The trailing return type is in the function's scope. This doesn't
make a difference for a simple case like int but it matters
for more complicated cases, like types declared in class scope or
types written in terms of the function parameters.
尾置返回类型位于函数的作用域内。这对于像 int 这样的简单情况没有区别,但对于更复杂的情况(如在类作用域中声明的类型或根据函数参数编写的类型)很重要。
Trailing return types are the only way to explicitly specify the return type of a lambda expression. In some cases the compiler is able to deduce a lambda's return type, but not in all cases. Even when the compiler can deduce it automatically, sometimes specifying it explicitly would be clearer for readers.
尾置返回类型是显式指定 lambda 表达式 返回类型的唯一方法。在某些情况下,编译器能够推断出 lambda 的返回类型,但并非在所有情况下都能推断出。即使编译器可以自动推断出它,有时显式指定它对读者来说也会更清楚。
Sometimes it's easier and more readable to specify a return type after the function's parameter list has already appeared. This is particularly true when the return type depends on template parameters. For example:
有时在函数的参数列表出现后指定返回类型更容易且更易读。当返回类型依赖于模板参数时尤其如此。例如:
template <typename T, typename U>
auto Add(T t, U u) -> decltype(t + u);
versus
template <typename T, typename U>
decltype(declval<T&>() + declval<U&>()) Add(T t, U u);
Trailing return type syntax has no analogue in C++-like languages such as C and Java, so some readers may find it unfamiliar.
尾置返回类型语法在 C 和 Java 等类 C++ 语言中没有类似物,因此一些读者可能会觉得陌生。
Existing codebases have an enormous number of function declarations that aren't going to get changed to use the new syntax, so the realistic choices are using the old syntax only or using a mixture of the two. Using a single version is better for uniformity of style.
现有的代码库有大量的函数声明不会更改为使用新语法,因此现实的选择是仅使用旧语法或混合使用两者。使用单一版本更有利于风格统一。
In most cases, continue to use the older style of function declaration where the return type goes before the function name. Use the new trailing-return-type form only in cases where it's required (such as lambdas) or where, by putting the type after the function's parameter list, it allows you to write the type in a much more readable way. The latter case should be rare; it's mostly an issue in fairly complicated template code, which is discouraged in most cases.
在大多数情况下,继续使用返回类型位于函数名之前的旧式函数声明。仅在需要时(例如 lambda)或通过将类型放在函数参数列表之后可以以更易读的方式编写类型的情况下,才使用新的尾置返回类型形式。后一种情况应该很少见;这主要是在相当复杂的模板代码中的问题,而在大多数情况下 不鼓励使用这种代码。
Google-Specific Magic 谷歌特有的魔法
There are various tricks and utilities that we use to make C++ code more robust, and various ways we use C++ that may differ from what you see elsewhere.
我们使用了各种技巧和实用程序来使 C++ 代码更加健壮,并且我们使用 C++ 的各种方式可能与你在其他地方看到的不同。
Ownership and Smart Pointers 所有权和智能指针
Prefer to have single, fixed owners for dynamically allocated objects. Prefer to transfer ownership with smart pointers.
最好让动态分配的对象有单一、固定的所有者。最好使用智能指针转移所有权。
"Ownership" is a bookkeeping technique for managing dynamically allocated memory (and other resources). The owner of a dynamically allocated object is an object or function that is responsible for ensuring that it is deleted when no longer needed. Ownership can sometimes be shared, in which case the last owner is typically responsible for deleting it. Even when ownership is not shared, it can be transferred from one piece of code to another.
“所有权”是一种用于管理动态分配的内存(和其他资源)的簿记技术。动态分配对象的所有者是负责确保在不再需要该对象时将其删除的对象或函数。所有权有时可以共享,在这种情况下,通常由最后一个所有者负责删除它。即使所有权不共享,它也可以从一段代码转移到另一段代码。
"Smart" pointers are classes that act like pointers,
e.g., by overloading the * and
-> operators. Some smart pointer types
can be used to automate ownership bookkeeping, to ensure
these responsibilities are met.
std::unique_ptr is a smart pointer type
which expresses exclusive ownership
of a dynamically allocated object; the object is deleted
when the std::unique_ptr goes out of scope.
It cannot be copied, but can be moved to
represent ownership transfer.
std::shared_ptr is a smart pointer type
that expresses shared ownership of
a dynamically allocated object. std::shared_ptrs
can be copied; ownership of the object is shared among
all copies, and the object is deleted when the last
std::shared_ptr is destroyed.
“智能”指针是行为类似于指针的类,例如通过重载 * 和 -> 运算符。某些智能指针类型可用于自动执行所有权簿记,以确保履行这些责任。std::unique_ptr 是一种智能指针类型,它表示对动态分配对象的独占所有权;当 std::unique_ptr 超出作用域时,该对象将被删除。它不能被复制,但可以被移动以表示所有权转移。std::shared_ptr 是一种智能指针类型,它表示对动态分配对象的共享所有权。std::shared_ptr 可以被复制;对象的所有权在所有副本之间共享,当最后一个 std::shared_ptr 被销毁时,该对象将被删除。
- It's virtually impossible to manage dynamically allocated memory without some sort of ownership logic.
- 如果没有某种所有权逻辑,几乎不可能管理动态分配的内存。
- Transferring ownership of an object can be cheaper than copying it (if copying it is even possible).
- 转移对象的所有权可能比复制它更便宜(如果甚至可以复制的话)。
- Transferring ownership can be simpler than 'borrowing' a pointer or reference, because it reduces the need to coordinate the lifetime of the object between the two users.
- 转移所有权可能比“借用”指针或引用更简单,因为它减少了在两个用户之间协调对象生命周期的需要。
- Smart pointers can improve readability by making ownership logic explicit, self-documenting, and unambiguous.
- 智能指针通过使所有权逻辑明确、自文档化和无歧义来提高可读性。
- Smart pointers can eliminate manual ownership bookkeeping, simplifying the code and ruling out large classes of errors.
- 智能指针可以消除手动所有权簿记,简化代码并排除大类错误。
- For
constobjects, shared ownership can be a simple and efficient alternative to deep copying. - 对于
const对象,共享所有权可以是深度复制的简单高效的替代方案。
- Ownership must be represented and transferred via pointers (whether smart or plain). Pointer semantics are more complicated than value semantics, especially in APIs: you have to worry not just about ownership, but also aliasing, lifetime, and mutability, among other issues.
- 所有权必须通过指针(无论是智能指针还是普通指针)来表示和转移。指针语义比值语义更复杂,尤其是在 API 中:你不仅要担心所有权,还要担心别名、生命周期和可变性等问题。
- The performance costs of value semantics are often overestimated, so the performance benefits of ownership transfer might not justify the readability and complexity costs.
- 值语义的性能成本通常被高估,因此所有权转移的性能优势可能无法证明可读性和复杂性成本是合理的。
- APIs that transfer ownership force their clients into a single memory management model.
- 转移所有权的 API 迫使其客户端进入单一的内存管理模型。
- Code using smart pointers is less explicit about where the resource releases take place.
- 使用智能指针的代码不太明确资源释放发生的位置。
std::unique_ptrexpresses ownership transfer using move semantics, which can be complex and may confuse some programmers.std::unique_ptr使用移动语义表示所有权转移,这可能很复杂,并且可能会让一些程序员感到困惑。- Shared ownership can be a tempting alternative to careful ownership design, obfuscating the design of a system.
- 共享所有权可能是仔细的所有权设计的一种诱人替代方案,这会混淆系统的设计。
- Shared ownership requires explicit bookkeeping at run-time, which can be costly.
- 共享所有权需要在运行时进行显式簿记,这可能会很昂贵。
- In some cases (e.g., cyclic references), objects with shared ownership may never be deleted.
- 在某些情况下(例如循环引用),具有共享所有权的对象可能永远不会被删除。
- Smart pointers are not perfect substitutes for plain pointers.
- 智能指针并不是普通指针的完美替代品。
If dynamic allocation is necessary, prefer to keep
ownership with the code that allocated it. If other code
needs access to the object, consider passing it a copy,
or passing a pointer or reference without transferring
ownership. Prefer to use std::unique_ptr to
make ownership transfer explicit. For example:
如果必须进行动态分配,最好将所有权保留在分配它的代码中。如果其他代码需要访问该对象,请考虑传递它的副本,或者在不转移所有权的情况下传递指针或引用。最好使用 std::unique_ptr 来明确所有权转移。例如:
std::unique_ptr<Foo> FooFactory(); void FooConsumer(std::unique_ptr<Foo> ptr);
Do not design your code to use shared ownership
without a very good reason. One such reason is to avoid
expensive copy operations, but you should only do this if
the performance benefits are significant, and the
underlying object is immutable (i.e.,
std::shared_ptr<const Foo>). If you
do use shared ownership, prefer to use
std::shared_ptr.
没有非常好的理由,不要将代码设计为使用共享所有权。其中一个理由是避免昂贵的复制操作,但只有在性能优势显著且底层对象不可变(即 std::shared_ptr<const Foo>)的情况下,你才应该这样做。如果你确实使用共享所有权,最好使用 std::shared_ptr。
Never use std::auto_ptr. Instead, use
std::unique_ptr.
永远不要使用 std::auto_ptr。相反,请使用 std::unique_ptr。
cpplint
Use cpplint.py to detect style errors.
使用 cpplint.py 检测风格错误。
cpplint.py
is a tool that reads a source file and identifies many
style errors. It is not perfect, and has both false
positives and false negatives, but it is still a valuable
tool.
cpplint.py 是一个读取源文件并识别许多风格错误的工具。它并不完美,既有误报也有漏报,但它仍然是一个有价值的工具。
Some projects have instructions on
how to run cpplint.py from their project
tools. If the project you are contributing to does not,
you can download
cpplint.py separately.
有些项目有关于如何从项目工具运行 cpplint.py 的说明。如果你贡献的项目没有,你可以单独下载 cpplint.py。
Other C++ Features 其他 C++ 特性
Rvalue References 右值引用
Use rvalue references only in certain special cases listed below.
仅在下列特定特殊情况下使用右值引用。
Rvalue references
are a type of reference that can only bind to temporary
objects. The syntax is similar to traditional reference
syntax. For example, void f(std::string&&
s); declares a function whose argument is an
rvalue reference to a std::string.
右值引用是一种只能绑定到临时对象的引用类型。其语法类似于传统的引用语法。例如,void f(std::string&& s); 声明了一个参数为 std::string 右值引用的函数。
When the token '&&' is applied to an unqualified template argument in a function parameter, special template argument deduction rules apply. Such a reference is called a forwarding reference.
当符号 '&&' 应用于函数参数中的未限定模板参数时,适用特殊的模板参数推导规则。这种引用称为转发引用。
- Defining a move constructor (a constructor taking
an rvalue reference to the class type) makes it
possible to move a value instead of copying it. If
v1is astd::vector<std::string>, for example, thenauto v2(std::move(v1))will probably just result in some simple pointer manipulation instead of copying a large amount of data. In many cases this can result in a major performance improvement. - 定义移动构造函数(接受类类型的右值引用的构造函数)使得移动值而不是复制值成为可能。例如,如果
v1是一个std::vector<std::string>,那么auto v2(std::move(v1))可能只会导致一些简单的指针操作,而不是复制大量数据。在许多情况下,这可以带来重大的性能提升。 - Rvalue references make it possible to implement types that are movable but not copyable, which can be useful for types that have no sensible definition of copying but where you might still want to pass them as function arguments, put them in containers, etc.
- 右值引用使得实现可移动但不可复制的类型成为可能,这对于那些没有合理的复制定义,但你可能仍想将它们作为函数参数传递、放入容器等的类型非常有用。
std::moveis necessary to make effective use of some standard-library types, such asstd::unique_ptr.std::move对于有效使用某些标准库类型(如std::unique_ptr)是必要的。- Forwarding references which use the rvalue reference token make it possible to write a generic function wrapper that forwards its arguments to another function, and works whether or not its arguments are temporary objects and/or const. This is called 'perfect forwarding'.
- 使用右值引用符号的 转发引用 使得编写一个通用函数包装器成为可能,该包装器将其参数转发给另一个函数,并且无论其参数是否为临时对象和/或 const 都能正常工作。这被称为“完美转发”。
- Rvalue references are not yet widely understood. Rules like reference collapsing and the special deduction rule for forwarding references are somewhat obscure.
- 右值引用尚未被广泛理解。引用折叠和转发引用的特殊推导规则等规则有些晦涩难懂。
- Rvalue references are often misused. Using rvalue references is counter-intuitive in signatures where the argument is expected to have a valid specified state after the function call, or where no move operation is performed.
- 右值引用经常被滥用。在参数预期在函数调用后具有有效指定状态或不执行移动操作的签名中使用右值引用是违反直觉的。
Do not use rvalue references (or apply the &&
qualifier to methods), except as follows:
不要使用右值引用(或将 && 限定符应用于方法),除非如下所示:
- You may use them to define move constructors and move assignment operators (as described in Copyable and Movable Types).
- 你可以使用它们来定义移动构造函数和移动赋值运算符(如 可拷贝和可移动类型 中所述)。
- You may use them to define
&&-qualified methods that logically "consume"*this, leaving it in an unusable or empty state. Note that this applies only to method qualifiers (which come after the closing parenthesis of the function signature); if you want to "consume" an ordinary function parameter, prefer to pass it by value. - 你可以使用它们来定义
&&限定的方法,这些方法在逻辑上“消耗”*this,使其处于不可用或空状态。请注意,这仅适用于方法限定符(位于函数签名闭括号之后);如果你想“消耗”一个普通函数参数,最好按值传递它。 - You may use forwarding references in conjunction with
std::forward, to support perfect forwarding. - 你可以结合使用转发引用和
std::forward来支持完美转发。 - You may use them to define pairs of overloads, such as one taking
Foo&&and the other takingconst Foo&. Usually the preferred solution is just to pass by value, but an overloaded pair of functions sometimes yields better performance, for example if the functions sometimes don't consume the input. As always: if you're writing more complicated code for the sake of performance, make sure you have evidence that it actually helps. - 你可以使用它们来定义重载对,例如一个接受
Foo&&,另一个接受const Foo&。通常首选的解决方案只是按值传递,但有时重载函数对会产生更好的性能,例如如果函数有时不消耗输入。一如既往:如果你为了性能而编写更复杂的代码,请确保你有证据表明它确实有帮助。
Friends 友元
We allow use of friend classes and functions,
within reason.
我们允许在合理范围内使用 friend 类和函数。
Friends should usually be defined in the same file so
that the reader does not have to look in another file to
find uses of the private members of a class. A common use
of friend is to have a
FooBuilder class be a friend of
Foo so that it can construct the inner state
of Foo correctly, without exposing this
state to the world. In some cases it may be useful to
make a unit test class a friend of the class it tests.
友元通常应该定义在同一个文件中,以便读者不必查看另一个文件来查找类的私有成员的使用。friend 的一个常见用途是让 FooBuilder 类成为 Foo 的友元,以便它可以正确构造 Foo 的内部状态,而无需向外界公开该状态。在某些情况下,让单元测试类成为它测试的类的友元可能很有用。
Friends extend, but do not break, the encapsulation
boundary of a class. In some cases this is better than
making a member public when you want to give only one
other class access to it. However, most classes should
interact with other classes solely through their public
members.
友元扩展了类的封装边界,但并没有打破它。在某些情况下,当你想只让另一个类访问某个成员时,这比将成员设为 public 更好。但是,大多数类应该仅通过其公共成员与其他类交互。
Exceptions 异常
We do not use C++ exceptions.
我们不使用 C++ 异常。
- Exceptions allow higher levels of an application to decide how to handle "can't happen" failures in deeply nested functions, without the obscuring and error-prone bookkeeping of error codes.
- 异常允许应用程序的更高层决定如何处理深度嵌套函数中“不可能发生”的故障,而无需进行晦涩且容易出错的错误代码簿记。
- Exceptions are used by most other modern languages. Using them in C++ would make it more consistent with Python, Java, and the C++ that others are familiar with.
- 大多数其他现代语言都使用异常。在 C++ 中使用它们将使其与 Python、Java 以及其他人熟悉的 C++更加一致。
- Some third-party C++ libraries use exceptions, and turning them off internally makes it harder to integrate with those libraries.
- 一些第三方 C++ 库使用异常,在内部关闭它们使得与这些库集成变得更加困难。
- Exceptions are the only way for a constructor to
fail. We can simulate this with a factory function or
an
Init()method, but these require heap allocation or a new "invalid" state, respectively. - 异常是构造函数失败的唯一途径。我们可以通过工厂函数或
Init()方法来模拟这一点,但这分别需要堆分配或新的“无效”状态。 - Exceptions are really handy in testing frameworks.
- 异常在测试框架中非常方便。
- When you add a
throwstatement to an existing function, you must examine all of its transitive callers. Either they must make at least the basic exception safety guarantee, or they must never catch the exception and be happy with the program terminating as a result. For instance, iff()callsg()callsh(), andhthrows an exception thatfcatches,ghas to be careful or it may not clean up properly. - 当你向现有函数添加
throw语句时,必须检查其所有传递调用者。它们要么必须至少提供基本的异常安全保证,要么必须永远不捕获异常,并乐于接受程序因此终止的结果。例如,如果f()调用g(),g()调用h(),并且h抛出了一个f捕获的异常,那么g必须小心,否则它可能无法正确清理。 - More generally, exceptions make the control flow of programs difficult to evaluate by looking at code: functions may return in places you don't expect. This causes maintainability and debugging difficulties. You can minimize this cost via some rules on how and where exceptions can be used, but at the cost of more that a developer needs to know and understand.
- 更一般地说,异常使得通过查看代码来评估程序的控制流变得困难:函数可能会在你意想不到的地方返回。这会导致可维护性和调试方面的困难。你可以通过关于如何以及在何处使用异常的一些规则来最小化此成本,但这需要开发人员了解和理解更多内容。
- Exception safety requires both RAII and different coding practices. Lots of supporting machinery is needed to make writing correct exception-safe code easy. Further, to avoid requiring readers to understand the entire call graph, exception-safe code must isolate logic that writes to persistent state into a "commit" phase. This will have both benefits and costs (perhaps where you're forced to obfuscate code to isolate the commit). Allowing exceptions would force us to always pay those costs even when they're not worth it.
- 异常安全既需要 RAII,也需要不同的编码实践。需要大量的支持机制才能轻松编写正确的异常安全代码。此外,为了避免要求读者理解整个调用图,异常安全代码必须将写入持久状态的逻辑隔离到“提交”阶段。这既有好处也有成本(也许你被迫混淆代码以隔离提交)。允许异常将迫使我们总是支付这些成本,即使它们不值得。
- Turning on exceptions adds data to each binary produced, increasing compile time (probably slightly) and possibly increasing address space pressure.
- 开启异常会向生成的每个二进制文件添加数据,增加编译时间(可能稍微增加),并可能增加地址空间压力。
- The availability of exceptions may encourage developers to throw them when they are not appropriate or recover from them when it's not safe to do so. For example, invalid user input should not cause exceptions to be thrown. We would need to make the style guide even longer to document these restrictions!
- 异常的可用性可能会鼓励开发人员在不合适的时候抛出异常,或者在不安全的时候从异常中恢复。例如,无效的用户输入不应导致抛出异常。我们将需要使风格指南更长来记录这些限制!
On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions.
从表面上看,使用异常的好处大于成本,尤其是在新项目中。但是,对于现有代码,引入异常会对所有依赖代码产生影响。如果异常可以传播到新项目之外,那么将新项目集成到现有的无异常代码中也会成为问题。因为 Google 的大多数现有 C++ 代码都没有准备好处理异常,所以采用生成异常的新代码相对困难。
Given that Google's existing code is not exception-tolerant, the costs of using exceptions are somewhat greater than the costs in a new project. The conversion process would be slow and error-prone. We don't believe that the available alternatives to exceptions, such as error codes and assertions, introduce a significant burden.
鉴于 Google 的现有代码不容忍异常,使用异常的成本在某种程度上大于新项目中的成本。转换过程将缓慢且容易出错。我们不认为异常的可用替代方案(如错误代码和断言)会带来沉重的负担。
Our advice against using exceptions is not predicated on philosophical or moral grounds, but practical ones. Because we'd like to use our open-source projects at Google and it's difficult to do so if those projects use exceptions, we need to advise against exceptions in Google open-source projects as well. Things would probably be different if we had to do it all over again from scratch.
我们不建议使用异常并非基于哲学或道德理由,而是基于实际理由。因为我们希望在 Google 使用我们的开源项目,如果这些项目使用异常,这就很难做到,所以我们也需要在 Google 开源项目中建议不要使用异常。如果我们必须从头再来,情况可能会有所不同。
This prohibition also applies to exception handling related
features such as std::exception_ptr and
std::nested_exception.
此禁令也适用于异常处理相关特性,如 std::exception_ptr 和 std::nested_exception。
There is an exception to this rule (no pun intended) for Windows code.
对于 Windows 代码,此规则有一个 例外(无意双关)。
noexcept
Specify noexcept when it is useful and correct.
当有用且正确时,请指定 noexcept。
The noexcept specifier is used to specify whether
a function will throw exceptions or not. If an exception
escapes from a function marked noexcept, the program
crashes via std::terminate.
noexcept 说明符用于指定函数是否会抛出异常。如果异常从标记为 noexcept 的函数中逃逸,程序将通过 std::terminate 崩溃。
The noexcept operator performs a compile-time
check that returns true if an expression is declared to not
throw any exceptions.
noexcept 运算符执行编译时检查,如果表达式声明为不抛出任何异常,则返回 true。
- Specifying move constructors as
noexceptimproves performance in some cases, e.g.,std::vector<T>::resize()moves rather than copies the objects if T's move constructor isnoexcept. - 将移动构造函数指定为
noexcept在某些情况下可以提高性能,例如,如果 T 的移动构造函数是noexcept的,则std::vector<T>::resize()会移动而不是复制对象。 - Specifying
noexcepton a function can trigger compiler optimizations in environments where exceptions are enabled, e.g., compiler does not have to generate extra code for stack-unwinding, if it knows that no exceptions can be thrown due to anoexceptspecifier. - 在启用了异常的环境中,在函数上指定
noexcept可以触发编译器优化,例如,如果编译器知道由于noexcept说明符而不会抛出异常,则不必为堆栈展开生成额外的代码。
-
In projects following this guide
that have exceptions disabled it is hard
to ensure that
noexceptspecifiers are correct, and hard to define what correctness even means. - 在遵循本指南且禁用了异常的项目中,很难确保
noexcept说明符是正确的,也很难定义正确性意味着什么。 - It's hard, if not impossible, to undo
noexceptbecause it eliminates a guarantee that callers may be relying on, in ways that are hard to detect. - 撤消
noexcept即使不是不可能,也很困难,因为它消除了调用者可能依赖的保证,而这种依赖方式很难检测。
You may use noexcept when it is useful for
performance if it accurately reflects the intended semantics
of your function, i.e., that if an exception is somehow thrown
from within the function body then it represents a fatal error.
You can assume that noexcept on move constructors
has a meaningful performance benefit. If you think
there is significant performance benefit from specifying
noexcept on some other function, please discuss it
with
your project leads.
当 noexcept 对性能有用时,如果它准确地反映了函数的预期语义,即如果异常以某种方式从函数体内部抛出,则它表示致命错误,你可以使用它。你可以假设移动构造函数上的 noexcept 具有有意义的性能优势。如果你认为在其他函数上指定 noexcept 有显著的性能优势,请与你的项目负责人讨论。
Prefer unconditional noexcept if exceptions are
completely disabled (i.e., most Google C++ environments).
Otherwise, use conditional noexcept specifiers
with simple conditions, in ways that evaluate false only in
the few cases where the function could potentially throw.
The tests might include type traits check on whether the
involved operation might throw (e.g.,
std::is_nothrow_move_constructible for
move-constructing objects), or on whether allocation can throw
(e.g., absl::default_allocator_is_nothrow for
standard default allocation). Note in many cases the only
possible cause for an exception is allocation failure (we
believe move constructors should not throw except due to
allocation failure), and there are many applications where it's
appropriate to treat memory exhaustion as a fatal error rather
than an exceptional condition that your program should attempt
to recover from. Even for other
potential failures you should prioritize interface simplicity
over supporting all possible exception throwing scenarios:
instead of writing a complicated noexcept clause
that depends on whether a hash function can throw, for example,
simply document that your component doesn't support hash
functions throwing and make it unconditionally
noexcept.
如果异常被完全禁用(即大多数 Google C++ 环境),则首选无条件 noexcept。否则,使用带有简单条件的条件 noexcept 说明符,使其仅在函数可能抛出的少数情况下计算为 false。测试可能包括类型特征检查,检查所涉及的操作是否可能抛出(例如,用于移动构造对象的 std::is_nothrow_move_constructible),或者检查分配是否可能抛出(例如,用于标准默认分配的 absl::default_allocator_is_nothrow)。请注意,在许多情况下,异常的唯一可能原因是分配失败(我们认为移动构造函数不应抛出异常,除非是由于分配失败),并且在许多应用程序中,将内存耗尽视为致命错误而不是程序应尝试恢复的异常情况是适当的。即使对于其他潜在的故障,你也应该优先考虑接口简单性,而不是支持所有可能的异常抛出场景:例如,与其编写一个取决于哈希函数是否可能抛出的复杂 noexcept 子句,不如简单地记录你的组件不支持哈希函数抛出,并使其无条件为 noexcept。
Run-Time Type Information (RTTI) 运行时类型信息 (RTTI)
Avoid using run-time type information (RTTI).
避免使用运行时类型信息 (RTTI)。
RTTI allows a
programmer to query the C++ class of an object at
run-time. This is done by use of typeid or
dynamic_cast.
RTTI 允许程序员在运行时查询对象的 C++ 类。这是通过使用 typeid 或 dynamic_cast 完成的。
The standard alternatives to RTTI (described below) require modification or redesign of the class hierarchy in question. Sometimes such modifications are infeasible or undesirable, particularly in widely-used or mature code.
RTTI 的标准替代方案(如下所述)需要修改或重新设计相关的类层次结构。有时此类修改是不可行的或不可取的,尤其是在广泛使用或成熟的代码中。
RTTI can be useful in some unit tests. For example, it is useful in tests of factory classes where the test has to verify that a newly created object has the expected dynamic type. It is also useful in managing the relationship between objects and their mocks.
RTTI 在某些单元测试中可能很有用。例如,在工厂类的测试中,测试必须验证新创建的对象是否具有预期的动态类型。在管理对象与其 mock 对象之间的关系时,它也很有用。
RTTI is useful when considering multiple abstract objects. Consider
当考虑多个抽象对象时,RTTI 很有用。考虑
bool Base::Equal(Base* other) = 0;
bool Derived::Equal(Base* other) {
Derived* that = dynamic_cast<Derived*>(other);
if (that == nullptr)
return false;
...
}
Querying the type of an object at run-time frequently means a design problem. Needing to know the type of an object at runtime is often an indication that the design of your class hierarchy is flawed.
在运行时查询对象的类型通常意味着设计问题。需要在运行时知道对象的类型通常表明类层次结构的设计存在缺陷。
Undisciplined use of RTTI makes code hard to maintain. It can lead to type-based decision trees or switch statements scattered throughout the code, all of which must be examined when making further changes.
不加约束地使用 RTTI 会使代码难以维护。它可能导致基于类型的决策树或 switch 语句分散在整个代码中,在进行进一步更改时必须检查所有这些决策树或 switch 语句。
RTTI has legitimate uses but is prone to abuse, so you must be careful when using it. You may use it freely in unit tests, but avoid it when possible in other code. In particular, think twice before using RTTI in new code. If you find yourself needing to write code that behaves differently based on the class of an object, consider one of the following alternatives to querying the type:
RTTI 有合法的用途,但容易被滥用,因此在使用时必须小心。你可以在单元测试中自由使用它,但在其他代码中尽可能避免使用它。特别是,在新代码中使用 RTTI 之前要三思。如果你发现自己需要根据对象的类编写行为不同的代码,请考虑以下查询类型的替代方案之一:
- Virtual methods are the preferred way of executing different code paths depending on a specific subclass type. This puts the work within the object itself.
- 虚方法是根据特定子类类型执行不同代码路径的首选方式。这将工作放在对象本身内。
- If the work belongs outside the object and instead in some processing code, consider a double-dispatch solution, such as the Visitor design pattern. This allows a facility outside the object itself to determine the type of class using the built-in type system.
- 如果工作属于对象外部,而是在某些处理代码中,请考虑双重分派解决方案,例如 Visitor 设计模式。这允许对象外部的设施使用内置类型系统确定类的类型。
When the logic of a program guarantees that a given
instance of a base class is in fact an instance of a
particular derived class, then a
dynamic_cast may be used freely on the
object. Usually one
can use a static_cast as an alternative in
such situations.
当程序逻辑保证基类的给定实例实际上是特定派生类的实例时,可以在对象上自由使用 dynamic_cast。通常在这种情况下可以使用 static_cast 作为替代方案。
Decision trees based on type are a strong indication that your code is on the wrong track.
基于类型的决策树有力地表明你的代码走错了方向。
if (typeid(*data) == typeid(D1)) {
...
} else if (typeid(*data) == typeid(D2)) {
...
} else if (typeid(*data) == typeid(D3)) {
...
Code such as this usually breaks when additional subclasses are added to the class hierarchy. Moreover, when properties of a subclass change, it is difficult to find and modify all the affected code segments.
当向类层次结构中添加其他子类时,此类代码通常会中断。此外,当子类的属性发生变化时,很难找到并修改所有受影响的代码段。
Do not hand-implement an RTTI-like workaround. The arguments against RTTI apply just as much to workarounds like class hierarchies with type tags. Moreover, workarounds disguise your true intent.
不要手动实现类似 RTTI 的变通方法。反对 RTTI 的论据同样适用于带有类型标签的类层次结构等变通方法。此外,变通方法掩盖了你的真实意图。
Casting 类型转换
Use C++-style casts
like static_cast<float>(double_value), or brace
initialization for conversion of arithmetic types like
int64_t y = int64_t{1} << 42. Do not use
cast formats like (int)x unless the cast is to
void. You may use cast formats like T(x) only when
T is a class type.
使用 C++ 风格的转换,如 static_cast<float>(double_value),或者使用大括号初始化来转换算术类型,如 int64_t y = int64_t{1} << 42。不要使用 (int)x 之类的转换格式,除非转换到 void。仅当 T 是类类型时,才可以使用 T(x) 之类的转换格式。
C++ introduced a different cast system from C that distinguishes the types of cast operations.
C++ 引入了与 C 不同的转换系统,该系统区分了转换操作的类型。
The problem with C casts is the ambiguity of the operation;
sometimes you are doing a conversion
(e.g., (int)3.5) and sometimes you are doing
a cast (e.g., (int)"hello"). Brace
initialization and C++ casts can often help avoid this
ambiguity. Additionally, C++ casts are more visible when searching for
them.
C 转换的问题在于操作的歧义性;有时你在进行 转换(conversion)(例如 (int)3.5),有时你在进行 强制转换(cast)(例如 (int)"hello")。大括号初始化和 C++ 转换通常有助于避免这种歧义。此外,C++ 转换在搜索时更显眼。
The C++-style cast syntax is verbose and cumbersome.
C++ 风格的转换语法冗长且繁琐。
In general, do not use C-style casts. Instead, use these C++-style casts when explicit type conversion is necessary.
一般情况下,不要使用 C 风格的转换。相反,当需要显式类型转换时,请使用这些 C++ 风格的转换。
- Use brace initialization to convert arithmetic types
(e.g.,
int64_t{x}). This is the safest approach because code will not compile if conversion can result in information loss. The syntax is also concise. - 使用大括号初始化来转换算术类型(例如
int64_t{x})。这是最安全的方法,因为如果转换可能导致信息丢失,代码将无法编译。语法也很简洁。 - When explicitly converting to a class type, use a function-style cast;
e.g., prefer
std::string(some_cord)tostatic_cast<std::string>(some_cord). - 显式转换为类类型时,使用函数式转换;例如,优先使用
std::string(some_cord)而不是static_cast<std::string>(some_cord)。 - Use
absl::implicit_castto safely cast up a type hierarchy, e.g., casting aFoo*to aSuperclassOfFoo*or casting aFoo*to aconst Foo*. C++ usually does this automatically but some situations need an explicit up-cast, such as use of the?:operator. - 使用
absl::implicit_cast在类型层次结构中安全地向上转换,例如将Foo*转换为SuperclassOfFoo*或将Foo*转换为const Foo*。C++ 通常会自动执行此操作,但某些情况需要显式向上转换,例如使用?:运算符。 - Use
static_castas the equivalent of a C-style cast that does value conversion, when you need to explicitly up-cast a pointer from a class to its superclass, or when you need to explicitly cast a pointer from a superclass to a subclass. In this last case, you must be sure your object is actually an instance of the subclass. - 当你需要显式地将指针从类向上转换为其超类,或者当你需要显式地将指针从超类转换为子类时,使用
static_cast作为进行值转换的 C 风格转换的等价物。在后一种情况下,你必须确保你的对象实际上是子类的实例。 - Use
const_castto remove theconstqualifier (see const). - 使用
const_cast移除const限定符(参见 const)。 - Use
reinterpret_castto do unsafe conversions of pointer types to and from integer and other pointer types, includingvoid*. Use this only if you know what you are doing and you understand the aliasing issues. Also, consider dereferencing the pointer (without a cast) and usingstd::bit_castto cast the resulting value. - 使用
reinterpret_cast在指针类型与整数和其他指针类型(包括void*)之间进行不安全的转换。仅当你确实知道自己在做什么并且了解别名问题时才使用此方法。此外,考虑解引用指针(不进行转换)并使用std::bit_cast来转换结果值。 - Use
std::bit_castto interpret the raw bits of a value using a different type of the same size (a type pun), such as interpreting the bits of adoubleasint64_t. - 使用
std::bit_cast用相同大小的不同类型来解释值的原始位(类型双关),例如将double的位解释为int64_t。
See the
RTTI section for guidance on the use of
dynamic_cast.
关于使用 dynamic_cast 的指导,请参阅 RTTI 章节。
Streams 流
Use streams where appropriate, and stick to "simple"
usages. Overload << for streaming only for types
representing values, and write only the user-visible value, not any
implementation details.
在适当的地方使用流,并坚持“简单”的用法。仅为表示值的类型重载 << 用于流式传输,并且仅写入用户可见的值,而不写入任何实现细节。
Streams are the standard I/O abstraction in C++, as
exemplified by the standard header <iostream>.
They are widely used in Google code, mostly for debug logging
and test diagnostics.
流是 C++ 中的标准 I/O 抽象,标准头文件 <iostream> 就是例证。它们在 Google 代码中广泛使用,主要用于调试日志记录和测试诊断。
The << and >>
stream operators provide an API for formatted I/O that
is easily learned, portable, reusable, and extensible.
printf, by contrast, doesn't even support
std::string, to say nothing of user-defined types,
and is very difficult to use portably.
printf also obliges you to choose among the
numerous slightly different versions of that function,
and navigate the dozens of conversion specifiers.
<< 和 >> 流运算符提供了一个易于学习、可移植、可重用且可扩展的格式化 I/O API。相比之下,printf 甚至不支持 std::string,更不用说用户定义的类型了,而且很难以可移植的方式使用。printf 还迫使你在该函数的众多略有不同的版本中进行选择,并浏览数十个转换说明符。
Streams provide first-class support for console I/O
via std::cin, std::cout,
std::cerr, and std::clog.
The C APIs do as well, but are hampered by the need to
manually buffer the input.
流通过 std::cin、std::cout、std::cerr 和 std::clog 为控制台 I/O 提供了一流的支持。C API 也可以,但受到需要手动缓冲输入的阻碍。
- Stream formatting can be configured by mutating the state of the stream. Such mutations are persistent, so the behavior of your code can be affected by the entire previous history of the stream, unless you go out of your way to restore it to a known state every time other code might have touched it. User code can not only modify the built-in state, it can add new state variables and behaviors through a registration system.
- 流格式化可以通过改变流的状态来配置。这种改变是持久的,因此你的代码行为可能会受到流的整个先前历史的影响,除非你特意在每次其他代码可能触及它时将其恢复到已知状态。用户代码不仅可以修改内置状态,还可以通过注册系统添加新的状态变量和行为。
- It is difficult to precisely control stream output, due to the above issues, the way code and data are mixed in streaming code, and the use of operator overloading (which may select a different overload than you expect).
- 由于上述问题、代码和数据在流式代码中混合的方式以及运算符重载的使用(可能会选择与你预期不同的重载),很难精确控制流输出。
- The practice of building up output through chains
of
<<operators interferes with internationalization, because it bakes word order into the code, and streams' support for localization is flawed. - 通过
<<运算符链构建输出的做法会干扰国际化,因为它将词序硬编码到代码中,而且流对本地化的支持是 有缺陷的。 - The streams API is subtle and complex, so programmers must develop experience with it in order to use it effectively.
- 流 API 微妙且复杂,因此程序员必须积累经验才能有效地使用它。
- Resolving the many overloads of
<<is extremely costly for the compiler. When used pervasively in a large codebase, it can consume as much as 20% of the parsing and semantic analysis time. - 解决
<<的许多重载对编译器来说非常昂贵。当在大型代码库中广泛使用时,它可能会消耗高达 20% 的解析和语义分析时间。
Use streams only when they are the best tool for the job.
This is typically the case when the I/O is ad-hoc, local,
human-readable, and targeted at other developers rather than
end-users. Be consistent with the code around you, and with the
codebase as a whole; if there's an established tool for
your problem, use that tool instead.
In particular,
logging libraries are usually a better
choice than std::cerr or std::clog
for diagnostic output, and the libraries in
absl/strings
or the equivalent are usually a
better choice than std::stringstream.
仅当流是完成工作的最佳工具时才使用流。这通常是在 I/O 是临时的、本地的、人类可读的,并且针对其他开发人员而不是最终用户时的情况。与周围的代码以及整个代码库保持一致;如果针对你的问题有既定的工具,请改用该工具。特别是,对于诊断输出,日志库通常是比 std::cerr 或 std::clog 更好的选择,并且 absl/strings 或等效库中的库通常是比 std::stringstream 更好的选择。
Avoid using streams for I/O that faces external users or handles untrusted data. Instead, find and use the appropriate templating libraries to handle issues like internationalization, localization, and security hardening.
避免对面向外部用户或处理不受信任数据的 I/O 使用流。相反,找到并使用适当的模板库来处理国际化、本地化和安全强化等问题。
If you do use streams, avoid the stateful parts of the
streams API (other than error state), such as imbue(),
xalloc(), and register_callback().
Use explicit formatting functions (such as
absl::StreamFormat()) rather than
stream manipulators or formatting flags to control formatting
details such as number base, precision, or padding.
如果你确实使用流,请避免使用流 API 的有状态部分(错误状态除外),例如 imbue()、xalloc() 和 register_callback()。使用显式格式化函数(例如 absl::StreamFormat())而不是流操纵器或格式化标志来控制格式化细节,例如数制、精度或填充。
Overload << as a streaming operator
for your type only if your type represents a value, and
<< writes out a human-readable string
representation of that value. Avoid exposing implementation
details in the output of <<; if you need to print
object internals for debugging, use named functions instead
(a method named DebugString() is the most common
convention).
仅当你的类型表示一个值,并且 << 写出该值的可读字符串表示形式时,才将 << 重载为你的类型的流运算符。避免在 << 的输出中暴露实现细节;如果你需要打印对象内部进行调试,请改用命名函数(名为 DebugString() 的方法是最常见的约定)。
Preincrement and Predecrement 前置自增和前置自减
Use the prefix form (++i) of the increment
and decrement operators unless you need postfix semantics.
使用前置形式 (++i) 的自增和自减运算符,除非你需要后置语义。
When a variable
is incremented (++i or i++) or
decremented (--i or i--) and
the value of the expression is not used, one must decide
whether to preincrement (decrement) or postincrement
(decrement).
当变量自增 (++i 或 i++) 或自减 (--i 或 i--) 且表达式的值未被使用时,必须决定是使用前置自增(自减)还是后置自增(自减)。
A postfix increment/decrement expression evaluates to the value as it was before it was modified. This can result in code that is more compact but harder to read. The prefix form is generally more readable, is never less efficient, and can be more efficient because it doesn't need to make a copy of the value as it was before the operation.
后置自增/自减表达式求值为修改前的值。这可能导致代码更紧凑但更难阅读。前置形式通常更具可读性,效率从不低于后置形式,而且可能更高效,因为它不需要在操作前复制该值。
The tradition developed, in C, of using post-increment, even
when the expression value is not used, especially in
for loops.
C 语言中形成了使用后置自增的传统,即使在不使用表达式值的情况下也是如此,尤其是在 for 循环中。
Use prefix increment/decrement, unless the code explicitly needs the result of the postfix increment/decrement expression.
使用前置自增/自减,除非代码明确需要后置自增/自减表达式的结果。
Use of const const 的使用
In APIs, use const whenever it makes sense.
constexpr is a better choice for some uses of
const.
在 API 中,只要有意义就使用 const。对于 const 的某些用途,constexpr 是更好的选择。
Declared variables and parameters can be preceded
by the keyword const to indicate the variables
are not changed (e.g., const int foo). Class
functions can have the const qualifier to
indicate the function does not change the state of the
class member variables (e.g., class Foo { int
Bar(char c) const; };).
声明的变量和参数可以在前面加上关键字 const,以表示变量未被更改(例如,const int foo)。类函数可以具有 const 限定符,以表示该函数不会更改类成员变量的状态(例如,class Foo { int Bar(char c) const; };)。
Easier for people to understand how variables are being used. Allows the compiler to do better type checking, and, conceivably, generate better code. Helps people convince themselves of program correctness because they know the functions they call are limited in how they can modify your variables. Helps people know what functions are safe to use without locks in multi-threaded programs.
使人们更容易理解变量的使用方式。允许编译器进行更好的类型检查,并且可以想象,生成更好的代码。帮助人们确信程序的正确性,因为他们知道他们调用的函数在修改变量方面受到限制。帮助人们知道在多线程程序中哪些函数可以安全地不加锁使用。
const is viral: if you pass a
const variable to a function, that function
must have const in its prototype (or the
variable will need a const_cast). This can
be a particular problem when calling library
functions.
const 具有传染性:如果你将 const 变量传递给函数,则该函数的原型中必须有 const(否则变量将需要 const_cast)。在调用库函数时,这可能是一个特别的问题。
We strongly recommend using const
in APIs (i.e., on function parameters, methods, and
non-local variables) wherever it is meaningful and accurate. This
provides consistent, mostly compiler-verified documentation
of what objects an operation can mutate. Having
a consistent and reliable way to distinguish reads from writes
is critical to writing thread-safe code, and is useful in
many other contexts as well. In particular:
我们强烈建议在 API 中(即在函数参数、方法和非局部变量上)只要有意义且准确就使用 const。这提供了关于操作可以改变哪些对象的一致的、主要是编译器验证的文档。拥有一种一致且可靠的方法来区分读和写对于编写线程安全代码至关重要,并且在许多其他上下文中也很有用。特别是:
- If a function guarantees that it will not modify an argument
passed by reference or by pointer, the corresponding function parameter
should be a reference-to-const (
const T&) or pointer-to-const (const T*), respectively. - 如果函数保证不会修改通过引用或指针传递的参数,则相应的函数参数应分别为 const 引用 (
const T&) 或 const 指针 (const T*)。 - For a function parameter passed by value,
consthas no effect on the caller, thus is not recommended in function declarations. See TotW #109. - 对于按值传递的函数参数,
const对调用者没有影响,因此不建议在函数声明中使用。参见 TotW #109。 - Declare methods to be
constunless they alter the logical state of the object (or enable the user to modify that state, e.g., by returning a non-constreference, but that's rare), or they can't safely be invoked concurrently. - 将方法声明为
const,除非它们更改对象的逻辑状态(或使用户能够修改该状态,例如通过返回非const引用,但这很少见),或者它们无法安全地并发调用。
Using const on local variables is neither encouraged
nor discouraged.
既不鼓励也不反对在局部变量上使用 const。
All of a class's const operations should be safe
to invoke concurrently with each other. If that's not feasible, the class must
be clearly documented as "thread-unsafe".
类的所有 const 操作都应该可以安全地相互并发调用。如果不可行,则必须将该类清楚地记录为“线程不安全”。
Where to put the const const 放哪里
Some people favor the form int const* foo
to const int* foo. They argue that this is
more readable because it's more consistent: it keeps the
rule that const always follows the object
it's describing. However, this consistency argument
doesn't apply in codebases with few deeply-nested pointer
expressions since most const expressions
have only one const, and it applies to the
underlying value. In such cases, there's no consistency
to maintain. Putting the const first is
arguably more readable, since it follows English in
putting the "adjective" (const) before the
"noun" (int).
有些人喜欢 int const* foo 形式胜过 const int* foo。他们认为这更具可读性,因为它更一致:它保持了 const 始终跟随它描述的对象的规则。但是,这种一致性论点不适用于几乎没有深层嵌套指针表达式的代码库,因为大多数 const 表达式只有一个 const,并且它适用于基础值。在这种情况下,没有一致性需要维护。将 const 放在前面可以说更具可读性,因为它遵循英语将“形容词”(const) 放在“名词”(int) 之前的习惯。
That said, while we encourage putting
const first, we do not require it. But be
consistent with the code around you!
话虽如此,虽然我们鼓励将 const 放在前面,但我们不强制要求这样做。但是要与周围的代码保持一致!
Use of constexpr, constinit, and consteval 使用 constexpr、constinit 和 consteval
Use constexpr to define true
constants or to ensure constant initialization.
Use constinit to ensure constant
initialization for non-constant variables.
使用 constexpr 定义真正常量或确保常量初始化。使用 constinit 确保非变量常量的常量初始化。
Some variables can be declared constexpr
to indicate the variables are true constants, i.e., fixed at
compilation/link time. Some functions and constructors
can be declared constexpr which enables them
to be used in defining a constexpr
variable. Functions can be declared consteval
to restrict their use to compile time.
某些变量可以声明为 constexpr,以表示这些变量是真正常量,即在编译/链接时固定。某些函数和构造函数可以声明为 constexpr,这使得它们能够用于定义 constexpr 变量。函数可以声明为 consteval 以限制其在编译时的使用。
Use of constexpr enables definition of
constants with floating-point expressions rather than
just literals; definition of constants of user-defined
types; and definition of constants with function
calls.
使用 constexpr 使得可以用浮点表达式而不仅仅是字面量定义常量;定义用户定义类型的常量;以及使用函数调用定义常量。
Prematurely marking something as constexpr may cause
migration problems if later on it has to be downgraded.
Current restrictions on what is allowed in constexpr
functions and constructors may invite obscure workarounds
in these definitions.
过早地将某物标记为 constexpr 可能会导致迁移问题,如果以后必须将其降级。目前对 constexpr 函数和构造函数中允许的内容的限制可能会导致这些定义中出现晦涩的变通方法。
constexpr definitions enable a more
robust specification of the constant parts of an
interface. Use constexpr to specify true
constants and the functions that support their
definitions. consteval may be used for
code that must not be invoked at runtime.
Avoid complexifying function definitions to
enable their use with constexpr. Do not use
constexpr or consteval to force inlining.
constexpr 定义使得接口的常量部分的规范更加健壮。使用 constexpr 指定真正常量以及支持其定义的函数。consteval 可用于不得在运行时调用的代码。避免为了使函数能够与 constexpr 一起使用而使函数定义复杂化。不要使用 constexpr 或 consteval 强制内联。
Integer Types 整数类型
Of the built-in C++ integer types, the only one used
is
int. If a program needs an integer type of a
different size, use an exact-width integer type from
<stdint.h>, such as
int16_t. If you have a
value that could ever be greater than or equal to 2^31,
use a 64-bit type such as int64_t.
Keep in mind that even if your value won't ever be too large
for an int, it may be used in intermediate
calculations which may require a larger type. When in doubt,
choose a larger type.
在内置的 C++ 整数类型中,唯一使用的是 int。如果程序需要不同大小的整数类型,请使用 <stdint.h> 中的精确宽度整数类型,例如 int16_t。如果你有一个可能大于或等于 2^31 的值,请使用 64 位类型,例如 int64_t。请记住,即使你的值对于 int 来说永远不会太大,它也可能用于可能需要更大类型的中间计算。如有疑问,请选择较大的类型。
C++ does not specify exact sizes for the integer types
like int. Common sizes on contemporary architectures are
16 bits for short, 32 bits for int, 32 or 64
bits for long, and 64 bits for long long,
but different platforms make different choices, in particular
for long.
C++ 没有指定 int 等整数类型的确切大小。当代架构上的常见大小是 short 为 16 位,int 为 32 位,long 为 32 或 64 位,long long 为 64 位,但不同的平台会做出不同的选择,特别是对于 long。
Uniformity of declaration.
声明的统一性。
The sizes of integral types in C++ can vary based on compiler and architecture.
C++ 中整数类型的大小可能会因编译器和架构而异。
The standard library header <stdint.h> defines types
like int16_t, uint32_t,
int64_t, etc. You should always use
those in preference to short, unsigned
long long, and the like, when you need a guarantee
on the size of an integer. Prefer to omit the std::
prefix for these types, as the extra 5 characters do
not merit the added clutter. Of the built-in integer types, only
int should be used. When appropriate, you
are welcome to use standard type aliases like
size_t and ptrdiff_t.
标准库头文件 <stdint.h> 定义了 int16_t、uint32_t、int64_t 等类型。当你需要保证整数大小时,你应该始终优先使用这些类型,而不是 short、unsigned long long 等。最好省略这些类型的 std:: 前缀,因为额外的 5 个字符不值得增加混乱。在内置整数类型中,应仅使用 int。在适当的时候,欢迎使用标准类型别名,如 size_t 和 ptrdiff_t。
We use int very often, for integers we
know are not going to be too big, e.g., loop counters.
Use plain old int for such things. You
should assume that an int is
at least 32 bits, but don't
assume that it has more than 32 bits. If you need a 64-bit
integer type, use int64_t or uint64_t.
For integers we know can be "big",
use
int64_t.
我们经常使用 int,用于我们知道不会太大的整数,例如循环计数器。对这些事情使用普通的旧式 int。你应该假设 int 至少为 32 位,但不要假设它超过 32 位。如果你需要 64 位整数类型,请使用 int64_t 或 uint64_t。
对于我们知道可能“很大”的整数,使用 int64_t。
You should not use the unsigned integer types such as
uint32_t, unless there is a valid
reason such as representing a bit pattern rather than a
number, or you need defined overflow modulo 2^N. In
particular, do not use unsigned types to say a number
will never be negative. Instead, use
assertions for this.
你不应该使用无符号整数类型(如 uint32_t),除非有正当理由,例如表示位模式而不是数字,或者你需要定义的模 2^N 溢出。特别是,不要使用无符号类型来表示数字永远不会为负数。相反,为此使用断言。
If your code is a container that returns a size, be sure to use a type that will accommodate any possible usage of your container. When in doubt, use a larger type rather than a smaller type.
如果你的代码是一个返回大小的容器,请务必使用一种能够适应容器任何可能用法的类型。如有疑问,请使用较大的类型而不是较小的类型。
Use care when converting integer types. Integer conversions and promotions can cause undefined behavior, leading to security bugs and other problems.
转换整数类型时要小心。整数转换和提升可能会导致未定义行为,从而导致安全漏洞和其他问题。
On Unsigned Integers 关于无符号整数
Unsigned integers are good for representing bitfields and modular arithmetic. Because of historical accident, the C++ standard also uses unsigned integers to represent the size of containers - many members of the standards body believe this to be a mistake, but it is effectively impossible to fix at this point. The fact that unsigned arithmetic doesn't model the behavior of a simple integer, but is instead defined by the standard to model modular arithmetic (wrapping around on overflow/underflow), means that a significant class of bugs cannot be diagnosed by the compiler. In other cases, the defined behavior impedes optimization.
无符号整数非常适合表示位域和模运算。由于历史原因,C++ 标准也使用无符号整数来表示容器的大小——标准机构的许多成员认为这是一个错误,但在这一点上实际上不可能修复。无符号算术不模拟简单整数的行为,而是由标准定义为模拟模算术(溢出/下溢时回绕),这意味着编译器无法诊断出一大类错误。在其他情况下,定义的行为会阻碍优化。
That said, mixing signedness of integer types is responsible for an equally large class of problems. The best advice we can provide: try to use iterators and containers rather than pointers and sizes, try not to mix signedness, and try to avoid unsigned types (except for representing bitfields or modular arithmetic). Do not use an unsigned type merely to assert that a variable is non-negative.
话虽如此,混合整数类型的符号也会导致同样大的一类问题。我们可以提供的最佳建议是:尝试使用迭代器和容器而不是指针和大小,尝试不要混合符号,并尝试避免无符号类型(除非用于表示位域或模运算)。不要仅仅为了断言变量非负而使用无符号类型。
Floating-Point Types 浮点类型
Of the built-in C++ floating-point types, the only ones used
are float and
double. You may assume that these types represent IEEE-754 binary32
and binary64, respectively.
在内置的 C++ 浮点类型中,唯一使用的是 float 和 double。你可以假设这些类型分别代表 IEEE-754 binary32 和 binary64。
Do not use long double, as it gives non-portable
results.
不要使用 long double,因为它的结果不具可移植性。
Architecture Portability 架构可移植性
Write architecture-portable code. Do not rely on CPU features specific to a single processor.
编写架构可移植的代码。不要依赖特定于单个处理器的 CPU 功能。
- When printing values, use type-safe numeric formatting libraries like
absl::StrCat,absl::Substitute,absl::StrFormat, orstd::ostreaminstead of theprintffamily of functions. - 打印值时,请使用类型安全的数字格式化库,如
absl::StrCat、absl::Substitute、absl::StrFormat或std::ostream,而不是printf系列函数。 - When moving structured data into or out of your process, encode it using a serialization library like Protocol Buffers rather than copying the in-memory representation around.
- 当将结构化数据移入或移出进程时,请使用像 Protocol Buffers 这样的序列化库对其进行编码,而不是到处复制内存中的表示形式。
- If you need to work with memory addresses as integers, store them in
uintptr_ts rather thanuint32_ts oruint64_ts. - 如果你需要将内存地址作为整数处理,请将它们存储在
uintptr_t中,而不是uint32_t或uint64_t中。 -
Use braced-initialization as needed to create
64-bit constants. For example:
int64_t my_value{0x123456789}; uint64_t my_mask{uint64_t{3} << 48}; -
根据需要使用 braced-initialization 来创建
64 位常量。例如:
int64_t my_value{0x123456789}; uint64_t my_mask{uint64_t{3} << 48}; - Use portable floating point types;
avoid
long double. - 使用可移植的 floating point types;
避免
long double。 - Use portable integer types; avoid
short,long, andlong long. - 使用可移植的 integer types;避免
short、long和long long。
Preprocessor Macros 预处理宏
Avoid defining macros, especially in headers; prefer
inline functions, enums, and const variables.
Name macros with a project-specific prefix. Do not use
macros to define pieces of a C++ API.
避免定义宏,尤其是在头文件中;首选内联函数、枚举和 const 变量。使用特定于项目的前缀命名宏。不要使用宏来定义 C++ API 的片段。
Macros mean that the code you see is not the same as the code the compiler sees. This can introduce unexpected behavior, especially since macros have global scope.
宏意味着你看到的代码与编译器看到的代码不同。这可能会引入意想不到的行为,特别是考虑到宏具有全局作用域。
The problems introduced by macros are especially severe when they are used to define pieces of a C++ API, and still more so for public APIs. Every error message from the compiler when developers incorrectly use that interface now must explain how the macros formed the interface. Refactoring and analysis tools have a dramatically harder time updating the interface. As a consequence, we specifically disallow using macros in this way. For example, avoid patterns like:
当宏用于定义 C++ API 的片段时,引入的问题尤其严重,对于公共 API 更是如此。开发人员错误地使用该接口时,编译器的每条错误消息现在都必须解释宏是如何形成接口的。重构和分析工具更新接口的难度大大增加。因此,我们特别禁止以这种方式使用宏。例如,避免如下模式:
class WOMBAT_TYPE(Foo) {
// ...
public:
EXPAND_PUBLIC_WOMBAT_API(Foo)
EXPAND_WOMBAT_COMPARISONS(Foo, ==, <)
};
Luckily, macros are not nearly as necessary in C++ as
they are in C. Instead of using a macro to inline
performance-critical code, use an inline function.
Instead of using a macro to store a constant, use a
const variable. Instead of using a macro to
"abbreviate" a long variable name, use a reference.
Instead of using a macro to conditionally compile code
... well, don't do that at all (except, of course, for
the #define guards to prevent double
inclusion of header files). It makes testing much more
difficult.
幸运的是,宏在 C++ 中并不像在 C 中那样必要。不要使用宏来内联性能关键代码,而是使用内联函数。不要使用宏来存储常量,而是使用 const 变量。不要使用宏来“缩写”长变量名,而是使用引用。不要使用宏来有条件地编译代码……好吧,根本不要这样做(当然,除了防止头文件双重包含的 #define 保护)。这使得测试更加困难。
Macros can do things these other techniques cannot, and you do see them in the codebase, especially in the lower-level libraries. And some of their special features (like stringifying, concatenation, and so forth) are not available through the language proper. But before using a macro, consider carefully whether there's a non-macro way to achieve the same result. If you need to use a macro to define an interface, contact your project leads to request a waiver of this rule.
宏可以做这些其他技术做不到的事情,你确实会在代码库中看到它们,尤其是在底层库中。它们的一些特殊功能(如字符串化、连接等)无法通过语言本身获得。但在使用宏之前,请仔细考虑是否有非宏方法可以达到相同的结果。如果你需要使用宏来定义接口,请联系你的项目负责人申请豁免此规则。
The following usage pattern will avoid many problems with macros; if you use macros, follow it whenever possible:
以下使用模式将避免宏的许多问题;如果你使用宏,请尽可能遵循它:
- Don't define macros in a
.hfile. - 不要在
.h文件中定义宏。 #definemacros right before you use them, and#undefthem right after.- 在使用宏之前立即
#define它们,并在使用后立即#undef它们。 - Do not just
#undefan existing macro before replacing it with your own; instead, pick a name that's likely to be unique. - 不要只是在用你自己的宏替换现有宏之前
#undef它;相反,选择一个可能唯一的名称。 - Try not to use macros that expand to unbalanced C++ constructs, or at least document that behavior well.
- 尽量不要使用扩展为不平衡 C++ 构造的宏,或者至少详细记录该行为。
- Prefer not using
##to generate function/class/variable names. - 最好不要使用
##生成函数/类/变量名称。
Exporting macros from headers (i.e., defining them in a header
without #undefing them before the end of the header)
is extremely strongly discouraged. If you do export a macro from a
header, it must have a globally unique name. To achieve this, it
must be named with a prefix consisting of your project's namespace
name (but upper case).
非常强烈不建议从头文件导出宏(即在头文件中定义它们而不在头文件结束前 #undef 它们)。如果你确实从头文件导出宏,它必须具有全局唯一的名称。为了实现这一点,它必须以由项目命名空间名称组成的前缀命名(但大写)。
0 and nullptr/NULL 0 和 nullptr/NULL
Use nullptr for pointers, and '\0' for chars (and
not the 0 literal).
对指针使用 nullptr,对字符使用 '\0'(而不是 0 字面量)。
For pointers (address values), use nullptr, as this
provides type-safety.
对于指针(地址值),使用 nullptr,因为这提供了类型安全。
Use '\0' for the null character. Using the correct type makes
the code more readable.
对空字符使用 '\0'。使用正确的类型使代码更具可读性。
sizeof
Prefer sizeof(varname) to
sizeof(type).
优先使用 sizeof(varname) 而不是 sizeof(type)。
Use sizeof(varname) when you
take the size of a particular variable.
sizeof(varname) will update
appropriately if someone changes the variable type either
now or later. You may use
sizeof(type) for code unrelated
to any particular variable, such as code that manages an
external or internal data format where a variable of an
appropriate C++ type is not convenient.
当你获取特定变量的大小时,使用 sizeof(varname)。如果有人现在或将来更改变量类型,sizeof(varname) 将适当地更新。你可以对与任何特定变量无关的代码使用 sizeof(type),例如管理外部或内部数据格式的代码,其中适当 C++ 类型的变量不方便。
MyStruct data; memset(&data, 0, sizeof(data));
memset(&data, 0, sizeof(MyStruct));
if (raw_size < sizeof(int)) {
LOG(ERROR) << "compressed record not big enough for count: " << raw_size;
return false;
}
Type Deduction (including auto) 类型推导(包括 auto)
Use type deduction only if it makes the code clearer to readers who aren't familiar with the project, or if it makes the code safer. Do not use it merely to avoid the inconvenience of writing an explicit type.
仅当类型推导使代码对不熟悉项目的读者更清晰,或使代码更安全时,才使用它。不要仅仅为了避免编写显式类型的不便而使用它。
There are several contexts in which C++ allows (or even requires) types to be deduced by the compiler, rather than spelled out explicitly in the code:
在几种上下文中,C++ 允许(甚至要求)编译器推导类型,而不是在代码中显式拼写出来:
- Function template argument deduction
- 函数模板参数推导
- A function template can be invoked without explicit template arguments.
The compiler deduces those arguments from the types of the function
arguments:
template <typename T> void f(T t); f(0); // Invokes f<int>(0)
- 函数模板可以在没有显式模板参数的情况下调用。编译器从函数参数的类型推导出这些参数:
template <typename T> void f(T t); f(0); // Invokes f<int>(0)
autovariable declarationsauto变量声明- A variable declaration can use the
autokeyword in place of the type. The compiler deduces the type from the variable's initializer, following the same rules as function template argument deduction with the same initializer (so long as you don't use curly braces instead of parentheses).auto a = 42; // a is an int auto& b = a; // b is an int& auto c = b; // c is an int auto d{42}; // d is an int, not a std::initializer_list<int>autocan be qualified withconst, and can be used as part of a pointer or reference type, and (since C++17) as a non-type template argument. A rare variant of this syntax usesdecltype(auto)instead ofauto, in which case the deduced type is the result of applyingdecltypeto the initializer. - 变量声明可以使用
auto关键字代替类型。编译器根据变量的初始化器推导类型,遵循与具有相同初始化器的函数模板参数推导相同的规则(只要你不使用花括号代替括号)。auto a = 42; // a is an int auto& b = a; // b is an int& auto c = b; // c is an int auto d{42}; // d is an int, not a std::initializer_list<int>auto可以用const限定,并且可以用作指针或引用类型的一部分,以及(自 C++17 起)作为非类型模板参数。这种语法的罕见变体使用decltype(auto)代替auto,在这种情况下,推导的类型是将decltype应用于初始化器的结果。 - Function return type deduction
- 函数返回类型推导
auto(anddecltype(auto)) can also be used in place of a function return type. The compiler deduces the return type from thereturnstatements in the function body, following the same rules as for variable declarations:auto f() { return 0; } // The return type of f is intLambda expression return types can be deduced in the same way, but this is triggered by omitting the return type, rather than by an explicitauto. Confusingly, trailing return type syntax for functions also usesautoin the return-type position, but that doesn't rely on type deduction; it's just an alternative syntax for an explicit return type.auto(和decltype(auto))也可以代替函数返回类型使用。编译器根据函数体中的return语句推导返回类型,遵循与变量声明相同的规则:auto f() { return 0; } // The return type of f is intLambda 表达式 返回类型可以以相同的方式推导,但这是通过省略返回类型而不是通过显式auto触发的。令人困惑的是,函数的 尾置返回类型 语法也在返回类型位置使用auto,但这不依赖于类型推导;它只是显式返回类型的替代语法。- Generic lambdas
- 泛型 Lambda
- A lambda expression can use the
autokeyword in place of one or more of its parameter types. This causes the lambda's call operator to be a function template instead of an ordinary function, with a separate template parameter for eachautofunction parameter:// Sort `vec` in decreasing order std::sort(vec.begin(), vec.end(), [](auto lhs, auto rhs) { return lhs > rhs; }); - Lambda 表达式可以使用
auto关键字代替其一个或多个参数类型。这会导致 lambda 的调用运算符成为函数模板而不是普通函数,每个auto函数参数都有一个单独的模板参数:// Sort `vec` in decreasing order std::sort(vec.begin(), vec.end(), [](auto lhs, auto rhs) { return lhs > rhs; }); - Lambda init captures
- Lambda 初始化捕获
- Lambda captures can have explicit initializers, which can be used to
declare wholly new variables rather than only capturing existing ones:
[x = 42, y = "foo"] { ... } // x is an int, and y is a const char*This syntax doesn't allow the type to be specified; instead, it's deduced using the rules forautovariables. - Lambda 捕获可以有显式的初始化器,可用于声明全新的变量,而不仅仅是捕获现有的变量:
[x = 42, y = "foo"] { ... } // x is an int, and y is a const char*这种语法不允许指定类型;相反,它是使用auto变量的规则推导出来的。 - Class template argument deduction
- 类模板参数推导
- See below.
- 参见 下文。
- Structured bindings
- 结构化绑定
- When declaring a tuple, struct, or array using
auto, you can specify names for the individual elements instead of a name for the whole object; these names are called "structured bindings", and the whole declaration is called a "structured binding declaration". This syntax provides no way of specifying the type of either the enclosing object or the individual names:auto [iter, success] = my_map.insert({key, value}); if (!success) { iter->second = value; }Theautocan also be qualified withconst,&, and&&, but note that these qualifiers technically apply to the anonymous tuple/struct/array, rather than the individual bindings. The rules that determine the types of the bindings are quite complex; the results tend to be unsurprising, except that the binding types typically won't be references even if the declaration declares a reference (but they will usually behave like references anyway). - 当使用
auto声明元组、结构体或数组时,你可以指定单个元素的名称,而不是整个对象的名称;这些名称称为“结构化绑定”,整个声明称为“结构化绑定声明”。这种语法无法指定封闭对象或单个名称的类型:auto [iter, success] = my_map.insert({key, value}); if (!success) { iter->second = value; }auto也可以用const、&和&&限定,但请注意,这些限定符在技术上适用于匿名元组/结构体/数组,而不是单个绑定。确定绑定类型的规则相当复杂;结果通常并不令人惊讶,除了绑定类型通常不会是引用,即使声明声明了引用(但无论如何它们通常表现得像引用)。
(These summaries omit many details and caveats; see the links for further information.)
(这些摘要省略了许多细节和注意事项;有关更多信息,请参阅链接。)
- C++ type names can be long and cumbersome, especially when they involve templates or namespaces.
- C++ 类型名称可能很长且麻烦,尤其是当它们涉及模板或命名空间时。
- When a C++ type name is repeated within a single declaration or a small code region, the repetition may not be aiding readability.
- 当 C++ 类型名称在单个声明或小代码区域中重复时,重复可能无助于可读性。
- It is sometimes safer to let the type be deduced, since that avoids the possibility of unintended copies or type conversions.
- 让类型被推导有时更安全,因为这避免了意外复制或类型转换的可能性。
C++ code is usually clearer when types are explicit, especially when type deduction would depend on information from distant parts of the code. In expressions like:
当类型显式时,C++ 代码通常更清晰,尤其是当类型推导依赖于代码远处部分的信息时。在如下表达式中:
auto foo = x.add_foo(); auto i = y.Find(key);
it may not be obvious what the resulting types are if the type
of y isn't very well known, or if y was
declared many lines earlier.
如果 y 的类型不是很清楚,或者如果 y 是在许多行之前声明的,那么结果类型可能并不明显。
Programmers have to understand when type deduction will or won't produce a reference type, or they'll get copies when they didn't mean to.
程序员必须了解类型推导何时会或不会产生引用类型,否则他们会在无意中获得副本。
If a deduced type is used as part of an interface, then a programmer might change its type while only intending to change its value, leading to a more radical API change than intended.
如果推导类型用作接口的一部分,那么程序员可能会在只打算更改其值时更改其类型,从而导致比预期更激进的 API 更改。
The fundamental rule is: use type deduction only to make the code
clearer or safer, and do not use it merely to avoid the
inconvenience of writing an explicit type. When judging whether the
code is clearer, keep in mind that your readers are not necessarily
on your team, or familiar with your project, so types that you and
your reviewer experience as unnecessary clutter will very often
provide useful information to others. For example, you can assume that
the return type of make_unique<Foo>() is obvious,
but the return type of MyWidgetFactory() probably isn't.
基本规则是:仅当类型推导使代码更清晰或更安全时才使用它,不要仅仅为了避免编写显式类型的不便而使用它。在判断代码是否更清晰时,请记住你的读者不一定在你的团队中,也不一定熟悉你的项目,因此你和你的审阅者认为是该去除的混乱类型通常会为其他人提供有用的信息。例如,你可以假设 make_unique<Foo>() 的返回类型是显而易见的,但 MyWidgetFactory() 的返回类型可能不是。
These principles apply to all forms of type deduction, but the details vary, as described in the following sections.
这些原则适用于所有形式的类型推导,但细节各不相同,如下节所述。
Function template argument deduction 函数模板参数推导
Function template argument deduction is almost always OK. Type deduction is the expected default way of interacting with function templates, because it allows function templates to act like infinite sets of ordinary function overloads. Consequently, function templates are almost always designed so that template argument deduction is clear and safe, or doesn't compile.
函数模板参数推导几乎总是可以的。类型推导是与函数模板交互的预期默认方式,因为它允许函数模板像无限的普通函数重载集一样行动。因此,函数模板的设计几乎总是使得模板参数推导清晰且安全,或者无法编译。
Local variable type deduction 局部变量类型推导
For local variables, you can use type deduction to make the code clearer by eliminating type information that is obvious or irrelevant, so that the reader can focus on the meaningful parts of the code:
对于局部变量,你可以使用类型推导来消除明显或不相关的类型信息,从而使代码更清晰,以便读者可以关注代码的有意义部分:
std::unique_ptr<WidgetWithBellsAndWhistles> widget =
std::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2);
absl::flat_hash_map<std::string,
std::unique_ptr<WidgetWithBellsAndWhistles>>::const_iterator
it = my_map_.find(key);
std::array<int, 6> numbers = {4, 8, 15, 16, 23, 42};
auto widget = std::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2);
auto it = my_map_.find(key);
std::array numbers = {4, 8, 15, 16, 23, 42};
Types sometimes contain a mixture of useful information and boilerplate,
such as it in the example above: it's obvious that the
type is an iterator, and in many contexts the container type and even the
key type aren't relevant, but the type of the values is probably useful.
In such situations, it's often possible to define local variables with
explicit types that convey the relevant information:
类型有时包含有用信息和样板代码的混合,例如上面的 it:很明显类型是迭代器,在许多上下文中,容器类型甚至键类型都不相关,但值的类型可能有用。在这种情况下,通常可以使用传达相关信息的显式类型定义局部变量:
if (auto it = my_map_.find(key); it != my_map_.end()) {
WidgetWithBellsAndWhistles& widget = *it->second;
// Do stuff with `widget`
}
If the type is a template instance, and the parameters are boilerplate but the template itself is informative, you can use class template argument deduction to suppress the boilerplate. However, cases where this actually provides a meaningful benefit are quite rare. Note that class template argument deduction is also subject to a separate style rule.
如果类型是模板实例,并且参数是样板代码但模板本身提供信息,你可以使用类模板参数推导来抑制样板代码。但是,这种情况实际上提供有意义好处的情况非常少见。请注意,类模板参数推导也受 单独的风格规则 约束。
Do not use decltype(auto) if a simpler option will work;
because it's a fairly obscure feature, it has a high cost in code
clarity.
如果更简单的选项可行,请不要使用 decltype(auto);因为它是一个相当晦涩的功能,所以在代码清晰度方面代价很高。
Return type deduction
Use return type deduction (for both functions and lambdas) only if the
function body has a very small number of return statements,
and very little other code, because otherwise the reader may not be able
to tell at a glance what the return type is. Furthermore, use it only
if the function or lambda has a very narrow scope, because functions with
deduced return types don't define abstraction boundaries: the implementation
is the interface. In particular, public functions in header files
should almost never have deduced return types.
仅当函数体具有非常少量的 return 语句且几乎没有其他代码时,才使用返回类型推导(对于函数和 lambda),否则读者可能无法一眼看出返回类型是什么。此外,仅当函数或 lambda 的范围非常窄时才使用它,因为具有推导返回类型的函数不定义抽象边界:实现 就是 接口。特别是,头文件中的公共函数几乎不应该有推导的返回类型。
Parameter type deduction
auto parameter types for lambdas should be used with caution,
because the actual type is determined by the code that calls the lambda,
rather than by the definition of the lambda. Consequently, an explicit
type will almost always be clearer unless the lambda is explicitly called
very close to where it's defined (so that the reader can easily see both),
or the lambda is passed to an interface so well-known that it's
obvious what arguments it will eventually be called with (e.g.,
the std::sort example above).
Lambda 的 auto 参数类型应谨慎使用,因为实际类型由调用 lambda 的代码确定,而不是由 lambda 的定义确定。因此,显式类型几乎总是更清晰,除非 lambda 在定义它的位置附近被显式调用(以便读者可以轻松看到两者),或者 lambda 传递给一个众所周知的接口,以至于很明显它最终将被调用的参数是什么(例如,上面的 std::sort 示例)。
Lambda init captures
Init captures are covered by a more specific style rule, which largely supersedes the general rules for type deduction.
初始化捕获由 更具体的风格规则 涵盖,该规则在很大程度上取代了类型推导的一般规则。
Structured bindings
Unlike other forms of type deduction, structured bindings can actually
give the reader additional information, by giving meaningful names to the
elements of a larger object. This means that a structured binding declaration
may provide a net readability improvement over an explicit type, even in cases
where auto would not. Structured bindings are especially
beneficial when the object is a pair or tuple (as in the insert
example above), because they don't have meaningful field names to begin with,
but note that you generally shouldn't use
pairs or tuples unless a pre-existing API like insert
forces you to.
与其他形式的类型推导不同,结构化绑定实际上可以通过为较大对象的元素提供有意义的名称来向读者提供更多信息。这意味着结构化绑定声明可以在显式类型之上提供净可读性改进,即使在 auto 不会的情况下也是如此。当对象是一对或元组时(如上面的 insert 示例),结构化绑定特别有益,因为它们一开始就没有有意义的字段名称,但请注意,通常 不应该使用对或元组,除非像 insert 这样的现有 API 强迫你这样做。
If the object being bound is a struct, it may sometimes be helpful to provide names that are more specific to your usage, but keep in mind that this may also mean the names are less recognizable to your reader than the field names. We recommend using a comment to indicate the name of the underlying field, if it doesn't match the name of the binding, using the same syntax as for function parameter comments:
如果绑定的对象是结构体,有时提供更具体于你用法的名称可能会有所帮助,但请记住,这也可能意味着这些名称对读者来说比字段名称更难识别。如果它与绑定的名称不匹配,我们建议使用注释来指示基础字段的名称,使用与函数参数注释相同的语法:
auto [/*field_name1=*/bound_name1, /*field_name2=*/bound_name2] = ...
As with function parameter comments, this can enable tools to detect if you get the order of the fields wrong.
与函数参数注释一样,这可以使用户工具检测你是否弄错了字段的顺序。
Class Template Argument Deduction 类模板参数推导
Use class template argument deduction only with templates that have explicitly opted into supporting it.
仅对已显式选择支持类模板参数推导的模板使用它。
Class template argument deduction (often abbreviated "CTAD") occurs when a variable is declared with a type that names a template, and the template argument list is not provided (not even empty angle brackets):
类模板参数推导(通常缩写为 "CTAD")发生在变量声明为命名模板的类型,并且未提供模板参数列表(甚至没有空尖括号)时:
std::array a = {1, 2, 3}; // `a` is a std::array<int, 3>
The compiler deduces the arguments from the initializer using the template's "deduction guides", which can be explicit or implicit.
编译器使用模板的“推导指南”(可以是显式的或隐式的)从初始化器推导参数。
Explicit deduction guides look like function declarations with trailing
return types, except that there's no leading auto, and the
function name is the name of the template. For example, the above example
relies on this deduction guide for std::array:
显式推导指南看起来像带有尾置返回类型的函数声明,除了没有前导 auto,并且函数名称是模板的名称。例如,上面的示例依赖于 std::array 的这个推导指南:
namespace std {
template <class T, class... U>
array(T, U...) -> std::array<T, 1 + sizeof...(U)>;
}
Constructors in a primary template (as opposed to a template specialization) also implicitly define deduction guides.
主模板(相对于模板特化)中的构造函数也隐式定义推导指南。
When you declare a variable that relies on CTAD, the compiler selects a deduction guide using the rules of constructor overload resolution, and that guide's return type becomes the type of the variable.
当你声明一个依赖于 CTAD 的变量时,编译器使用构造函数重载解析规则选择一个推导指南,该指南的返回类型成为变量的类型。
CTAD can sometimes allow you to omit boilerplate from your code.
CTAD 有时允许你从代码中省略样板代码。
The implicit deduction guides that are generated from constructors may have undesirable behavior, or be outright incorrect. This is particularly problematic for constructors written before CTAD was introduced in C++17, because the authors of those constructors had no way of knowing about (much less fixing) any problems that their constructors would cause for CTAD. Furthermore, adding explicit deduction guides to fix those problems might break any existing code that relies on the implicit deduction guides.
从构造函数生成的隐式推导指南可能有不良行为,或者是完全错误的。这对于在 C++17 引入 CTAD 之前编写的构造函数来说尤其成问题,因为这些构造函数的作者无法知道(更不用说修复)他们的构造函数会对 CTAD 造成的任何问题。此外,添加显式推导指南以修复这些问题可能会破坏依赖隐式推导指南的任何现有代码。
CTAD also suffers from many of the same drawbacks as auto,
because they are both mechanisms for deducing all or part of a variable's
type from its initializer. CTAD does give the reader more information
than auto, but it also doesn't give the reader an obvious
cue that information has been omitted.
CTAD 也遭受许多与 auto 相同的缺点,因为它们都是从初始化器推导变量类型的全部或部分的机制。CTAD 确实给读者提供了比 auto 更多的信息,但它也没有给读者一个明显的提示,即信息已被省略。
Do not use CTAD with a given template unless the template's maintainers
have opted into supporting use of CTAD by providing at least one explicit
deduction guide (all templates in the std namespace are
also presumed to have opted in). This should be enforced with a compiler
warning if available.
不要对给定模板使用 CTAD,除非模板的维护者通过提供至少一个显式推导指南(也被假定为已选择加入 std 命名空间中的所有模板)已选择支持使用 CTAD。如果可用,这应该通过编译器警告来强制执行。
Uses of CTAD must also follow the general rules on Type deduction.
CTAD 的使用还必须遵循 类型推导 的一般规则。
Designated Initializers 指定初始化器
Use designated initializers only in their C++20-compliant form.
仅以符合 C++20 的形式使用指定初始化器。
Designated initializers are a syntax that allows for initializing an aggregate ("plain old struct") by naming its fields explicitly:
指定初始化器 是一种允许通过显式命名字段来初始化聚合(“普通旧结构体”)的语法:
struct Point {
float x = 0.0;
float y = 0.0;
float z = 0.0;
};
Point p = {
.x = 1.0,
.y = 2.0,
// z will be 0.0
};
The explicitly listed fields will be initialized as specified, and others
will be initialized in the same way they would be in a traditional aggregate
initialization expression like Point{1.0, 2.0}.
显式列出的字段将按指定进行初始化,其他字段将以与传统聚合初始化表达式(如 Point{1.0, 2.0})相同的方式进行初始化。
Designated initializers can make for convenient and highly readable
aggregate expressions, especially for structs with less straightforward
ordering of fields than the Point example above.
指定初始化器可以使聚合表达式方便且高度可读,特别是对于字段排序不如上面的 Point 示例那样直观的结构体。
While designated initializers have long been part of the C standard and supported by C++ compilers as an extension, they were not supported by C++ prior to C++20.
虽然指定初始化器长期以来一直是 C 标准的一部分,并作为扩展受 C++ 编译器支持,但在 C++20 之前,C++ 并不支持它们。
The rules in the C++ standard are stricter than in C and compiler extensions,
requiring that the designated initializers appear in the same order as the
fields appear in the struct definition. So in the example above, it is legal
according to C++20 to initialize x and then z, but not
y and then x.
C++ 标准中的规则比 C 和编译器扩展中的规则更严格,要求指定初始化器以与结构体定义中字段出现的相同顺序出现。因此,在上面的示例中,根据 C++20,初始化 x 然后初始化 z 是合法的,但初始化 y 然后初始化 x 是不合法的。
Use designated initializers only in the form that is compatible with the C++20 standard: with initializers in the same order as the corresponding fields appear in the struct definition.
仅以与 C++20 标准兼容的形式使用指定初始化器:初始化器的顺序与结构体定义中对应字段出现的顺序相同。
Lambda Expressions Lambda 表达式
Use lambda expressions where appropriate. Prefer explicit captures when the lambda will escape the current scope.
在适当的地方使用 lambda 表达式。当 lambda 逃逸出当前作用域时,首选显式捕获。
Lambda expressions are a concise way of creating anonymous function objects. They're often useful when passing functions as arguments. For example:
Lambda 表达式是创建匿名函数对象的简洁方式。在将函数作为参数传递时,它们通常很有用。例如:
std::sort(v.begin(), v.end(), [](int x, int y) {
return Weight(x) < Weight(y);
});
They further allow capturing variables from the enclosing scope either explicitly by name, or implicitly using a default capture. Explicit captures require each variable to be listed, as either a value or reference capture:
它们还允许通过名称显式捕获或使用默认捕获隐式捕获封闭作用域中的变量。显式捕获要求列出每个变量,无论是值捕获还是引用捕获:
int weight = 3;
int sum = 0;
// Captures `weight` by value and `sum` by reference.
std::for_each(v.begin(), v.end(), [weight, &sum](int x) {
sum += weight * x;
});
Default captures implicitly capture any variable referenced in the
lambda body, including this if any members are used:
默认捕获隐式捕获 lambda 主体中引用的任何变量,如果使用了任何成员,则包括 this:
const std::vector<int> lookup_table = ...;
std::vector<int> indices = ...;
// Captures `lookup_table` by reference, sorts `indices` by the value
// of the associated element in `lookup_table`.
std::sort(indices.begin(), indices.end(), [&](int a, int b) {
return lookup_table[a] < lookup_table[b];
});
A variable capture can also have an explicit initializer, which can be used for capturing move-only variables by value, or for other situations not handled by ordinary reference or value captures:
变量捕获也可以具有显式初始化器,可用于按值捕获仅移动变量,或用于普通引用或值捕获未处理的其他情况:
std::unique_ptr<Foo> foo = ...;
[foo = std::move(foo)] () {
...
}
Such captures (often called "init captures" or "generalized lambda captures") need not actually "capture" anything from the enclosing scope, or even have a name from the enclosing scope; this syntax is a fully general way to define members of a lambda object:
此类捕获(通常称为“初始化捕获”或“广义 lambda 捕获”)实际上不需要从封闭作用域“捕获”任何内容,甚至不需要具有封闭作用域中的名称;这种语法是定义 lambda 对象成员的完全通用方式:
[foo = std::vector<int>({1, 2, 3})] () {
...
}
The type of a capture with an initializer is deduced using the same rules
as auto.
具有初始化器的捕获类型是使用与 auto 相同的规则推导出来的。
- Lambdas are much more concise than other ways of defining function objects to be passed to STL algorithms, which can be a readability improvement.
- Lambda 比定义传递给 STL 算法的函数对象的其他方式简洁得多,这可以提高可读性。
- Appropriate use of default captures can remove redundancy and highlight important exceptions from the default.
- 适当使用默认捕获可以消除冗余并突出显示与默认情况的重要例外。
- Lambdas,
std::function, andstd::bindcan be used in combination as a general purpose callback mechanism; they make it easy to write functions that take bound functions as arguments. - Lambda、
std::function和std::bind可以组合使用作为通用回调机制;它们使得编写将绑定函数作为参数的函数变得容易。
- Variable capture in lambdas can be a source of dangling-pointer bugs, particularly if a lambda escapes the current scope.
- Lambda 中的变量捕获可能是悬空指针错误的来源,特别是如果 lambda 逃逸出当前作用域。
- Default captures by value can be misleading because they do not prevent
dangling-pointer bugs. Capturing a pointer by value doesn't cause a deep
copy, so it often has the same lifetime issues as capture by reference.
This is especially confusing when capturing
thisby value, since the use ofthisis often implicit. - 按值默认捕获可能会产生误导,因为它们不能防止悬空指针错误。按值捕获指针不会导致深层复制,因此它通常具有与按引用捕获相同的生命周期问题。当按值捕获
this时,这尤其令人困惑,因为this的使用通常是隐式的。 - Captures actually declare new variables (whether or not the captures have
initializers), but they look nothing like any other variable declaration
syntax in C++. In particular, there's no place for the variable's type,
or even an
autoplaceholder (although init captures can indicate it indirectly, e.g., with a cast). This can make it difficult to even recognize them as declarations. - 捕获实际上声明了新变量(无论捕获是否具有初始化器),但它们看起来一点也不像 C++ 中的任何其他变量声明语法。特别是,没有地方放置变量的类型,甚至没有
auto占位符(虽然初始化捕获可以间接指示它,例如,通过转换)。这可能使得甚至很难将它们识别为声明。 - Init captures inherently rely on type
deduction, and suffer from many of the same drawbacks as
auto, with the additional problem that the syntax doesn't even cue the reader that deduction is taking place. - 初始化捕获本质上依赖于 类型推导,并遭受许多与
auto相同的缺点,还有一个额外的问题,即语法甚至没有提示读者正在进行推导。 - It's possible for use of lambdas to get out of hand; very long nested anonymous functions can make code harder to understand.
- Lambda 的使用可能会失控;非常长的嵌套匿名函数会使代码更难理解。
- Use lambda expressions where appropriate, with formatting as described below.
- 在适当的地方使用 lambda 表达式,格式如下所述 下文。
- Prefer explicit captures if the lambda may escape the current scope.
For example, instead of:
{ Foo foo; ... executor->Schedule([&] { Frobnicate(foo); }) ... } // BAD! The fact that the lambda makes use of a reference to `foo` and // possibly `this` (if `Frobnicate` is a member function) may not be // apparent on a cursory inspection. If the lambda is invoked after // the function returns, that would be bad, because both `foo` // and the enclosing object could have been destroyed.prefer to write:{ Foo foo; ... executor->Schedule([&foo] { Frobnicate(foo); }) ... } // BETTER - The compile will fail if `Frobnicate` is a member // function, and it's clearer that `foo` is dangerously captured by // reference. - 如果 lambda 可能逃逸出当前作用域,请首选显式捕获。例如,不要这样写:
{ Foo foo; ... executor->Schedule([&] { Frobnicate(foo); }) ... } // BAD! The fact that the lambda makes use of a reference to `foo` and // possibly `this` (if `Frobnicate` is a member function) may not be // apparent on a cursory inspection. If the lambda is invoked after // the function returns, that would be bad, because both `foo` // and the enclosing object could have been destroyed.最好这样写:{ Foo foo; ... executor->Schedule([&foo] { Frobnicate(foo); }) ... } // BETTER - The compile will fail if `Frobnicate` is a member // function, and it's clearer that `foo` is dangerously captured by // reference. - Use default capture by reference (
[&]) only when the lifetime of the lambda is obviously shorter than any potential captures. - 仅当 lambda 的生命周期明显短于任何潜在捕获时,才使用按引用默认捕获 (
[&])。 - Use default capture by value (
[=]) only as a means of binding a few variables for a short lambda, where the set of captured variables is obvious at a glance, and which does not result in capturingthisimplicitly. (That means that a lambda that appears in a non-static class member function and refers to non-static class members in its body must capturethisexplicitly or via[&].) Prefer not to write long or complex lambdas with default capture by value. - 仅当作为绑定短 lambda 的几个变量的一种手段时,才使用按值默认捕获 (
[=]),其中捕获的变量集一目了然,并且不会导致隐式捕获this。(这意味着出现在非静态类成员函数中并在其主体中引用非静态类成员的 lambda 必须显式捕获this或通过[&]捕获。)尽量不要编写带有按值默认捕获的长或复杂 lambda。 - Use captures only to actually capture variables from the enclosing scope. Do not use captures with initializers to introduce new names, or to substantially change the meaning of an existing name. Instead, declare a new variable in the conventional way and then capture it, or avoid the lambda shorthand and define a function object explicitly.
- 仅使用捕获来实际捕获封闭作用域中的变量。不要使用带有初始化器的捕获来引入新名称,或实质性地改变现有名称的含义。相反,以常规方式声明新变量,然后捕获它,或者避免 lambda 简写并显式定义函数对象。
- See the section on type deduction for guidance on specifying the parameter and return types.
- 有关指定参数和返回类型的指导,请参阅 类型推导 一节。
Template Metaprogramming 模板元编程
Avoid complicated template programming.
避免复杂的模板编程。
Template metaprogramming refers to a family of techniques that exploit the fact that the C++ template instantiation mechanism is Turing complete and can be used to perform arbitrary compile-time computation in the type domain.
模板元编程是指一类技术,它利用 C++ 模板实例化机制具有图灵完备性这一事实,从而能够在类型域中执行任意的编译期计算。
Template metaprogramming allows extremely flexible interfaces that
are type safe and high performance. Facilities like
GoogleTest,
std::tuple, std::function, and
Boost.Spirit would be impossible without it.
模板元编程能够提供极其灵活、类型安全且高性能的接口。没有它,诸如
GoogleTest、
std::tuple、std::function 以及 Boost.Spirit 之类的设施将难以实现。
The techniques used in template metaprogramming are often obscure to anyone but language experts. Code that uses templates in complicated ways is often unreadable, and is hard to debug or maintain.
模板元编程所使用的技术往往只有语言专家才熟悉。以复杂方式使用模板的代码通常难以阅读,也难以调试和维护。
Template metaprogramming often leads to extremely poor compile time error messages: even if an interface is simple, the complicated implementation details become visible when the user does something wrong.
模板元编程经常导致非常糟糕的编译期错误信息:即使接口很简单,当用户用错时,复杂的实现细节也会暴露出来。
Template metaprogramming interferes with large scale refactoring by making the job of refactoring tools harder. First, the template code is expanded in multiple contexts, and it's hard to verify that the transformation makes sense in all of them. Second, some refactoring tools work with an AST that only represents the structure of the code after template expansion. It can be difficult to automatically work back to the original source construct that needs to be rewritten.
模板元编程会干扰大规模重构,因为它让重构工具的工作更困难。首先,模板代码会在多个上下文中展开,很难验证变换在所有上下文里都合理。其次,一些重构工具使用的 AST 只表示模板展开后的代码结构,很难自动回溯到需要改写的原始源代码结构。
Template metaprogramming sometimes allows cleaner and easier-to-use interfaces than would be possible without it, but it's also often a temptation to be overly clever. It's best used in a small number of low level components where the extra maintenance burden is spread out over a large number of uses.
模板元编程有时能带来比不使用它更简洁、更易用的接口,但它也常常诱使人过度耍“聪明”。它最适合用于少量底层组件,在这些组件中额外的维护负担可以分摊到大量使用场景上。
Think twice before using template metaprogramming or other
complicated template techniques; think about whether the average
member of your team will be able to understand your code well enough
to maintain it after you switch to another project, or whether a
non-C++ programmer or someone casually browsing the codebase will be
able to understand the error messages or trace the flow of a function
they want to call. If you're using recursive template instantiations
or type lists or metafunctions or expression templates, or relying on
SFINAE or on the sizeof trick for detecting function
overload resolution, then there's a good chance you've gone too
far.
在使用模板元编程或其他复杂模板技术之前要三思:想想当你转到另一个项目后,你团队中的普通成员是否能足够理解这段代码并维护它;或者对于非 C++ 程序员、以及随便浏览代码库的人来说,他们是否能理解错误信息,或追踪他们想调用的函数流程。如果你在使用递归模板实例化、类型列表、元函数、表达式模板,或依赖 SFINAE、或使用 sizeof 技巧来实现函数重载解析探测,那么你很可能已经走得太远了。
If you use template metaprogramming, you should expect to put considerable effort into minimizing and isolating the complexity. You should hide metaprogramming as an implementation detail whenever possible, so that user-facing headers are readable, and you should make sure that tricky code is especially well commented. You should carefully document how the code is used, and you should say something about what the "generated" code looks like. Pay extra attention to the error messages that the compiler emits when users make mistakes. The error messages are part of your user interface, and your code should be tweaked as necessary so that the error messages are understandable and actionable from a user point of view.
如果你使用模板元编程,就应该预期需要投入相当多精力来最小化并隔离其复杂度。应尽可能把元编程隐藏为实现细节,使面向用户的头文件保持可读;并确保棘手的代码有充分注释。应仔细文档化代码的使用方式,并说明“生成”的代码大致长什么样。还要格外关注用户出错时编译器发出的错误信息:错误信息是你用户界面的一部分,你应按需调整代码,使错误信息从用户角度看是可理解且可操作的。
Concepts and Constraints 概念与约束
Use concepts sparingly.
In general, concepts and constraints should only be used in cases
where templates would have been used prior to C++20.
Avoid introducing new concepts in headers,
unless the headers are marked as internal to the library.
Do not define concepts that are not enforced by the compiler.
Prefer constraints over template metaprogramming, and
avoid the template<Concept T> syntax;
instead, use the requires(Concept<T>)
syntax.
谨慎使用概念(concept)。一般而言,概念和约束只应当用于在 C++20 之前本来就会使用模板的场景。避免在头文件中引入新的概念,除非该头文件标记为库的内部头文件。不要定义那些不会被编译器强制验证的概念。相较于模板元编程,更偏好使用约束(constraint);并避免 template<Concept T> 语法,而改用 requires(Concept<T>) 语法。
The concept keyword is a new mechanism for defining
requirements (such as type traits or interface specifications)
for a template parameter.
The requires keyword provides mechanisms for placing
anonymous constraints on templates and verifying that constraints
are satisfied at compile time.
Concepts and constraints are often used together, but can be
also used independently.
concept 关键字是一种为模板参数定义需求(例如类型特征或接口规范)的新机制。requires 关键字提供了在模板上施加匿名约束并在编译期验证约束是否满足的机制。概念和约束经常配合使用,但也可以独立使用。
- Concepts allow the compiler to generate much better error messages when templates are involved, which can reduce confusion and significantly improve the development experience.
- 概念使得在涉及模板时编译器能够生成更好的错误信息,从而减少困惑并显著提升开发体验。
- Concepts can reduce the boilerplate necessary for defining and using compile-time constraints, often increasing the clarity of the resulting code.
- 概念可以减少定义和使用编译期约束所需的样板代码,通常能提升最终代码的清晰度。
- Constraints provide some capabilities that are difficult to achieve with templates and SFINAE techniques.
- 约束提供了一些用模板与 SFINAE 技术难以实现的能力。
- As with templates, concepts can make code significantly more complex and difficult to understand.
- 与模板类似,概念可能会让代码显著更复杂、更难理解。
- Concept syntax can be confusing to readers, as concepts appear similar to class types at their usage sites.
- 概念语法可能会让读者困惑,因为概念在使用处看起来很像类类型。
- Concepts, especially at API boundaries, increase code coupling, rigidity, and ossification.
- 概念(尤其在 API 边界处)会增加代码的耦合、刚性和固化。
- Concepts and constraints can replicate logic from a function body, resulting in code duplication and increased maintenance costs.
- 概念和约束可能会重复函数体中的逻辑,从而造成代码重复并增加维护成本。
- Concepts muddy the source of truth for their underlying contracts, as they are standalone named entities that can be utilized in multiple locations, all of which evolve separately from each other. This can cause the stated and implied requirements to diverge over time.
- 概念会模糊其底层契约的“事实来源”,因为它们是独立的命名实体,可以在多个位置被使用,而这些位置彼此独立演化。这会导致显式陈述的需求与隐含需求随时间发生偏离。
- Concepts and constraints affect overload resolution in novel and non-obvious ways.
- 概念和约束会以新的、且不那么直观的方式影响重载解析。
- As with SFINAE, constraints make it harder to refactor code at scale.
- 与 SFINAE 类似,约束会让大规模重构更困难。
Predefined concepts in the standard library should be
preferred to type traits, when equivalent ones exist.
(e.g., if std::is_integral_v would have been used
before C++20, then std::integral should be used in
C++20 code.)
Similarly, prefer modern constraint syntax
(via requires(Condition)).
Avoid legacy template metaprogramming constructs
(such as std::enable_if<Condition>)
as well as the template<Concept T>
syntax.
当标准库中存在等价的预定义概念时,应优先使用它们而不是类型特征。(例如:如果在 C++20 之前会使用 std::is_integral_v,那么在 C++20 代码中应使用 std::integral。)同样,应优先使用现代约束语法(通过 requires(Condition))。避免使用传统的模板元编程构造(例如 std::enable_if<Condition>)以及 template<Concept T> 语法。
Do not manually re-implement any existing concepts or traits.
For example, use
requires(std::default_initializable<T>)
instead of
requires(requires { T v; })
or the like.
New concept declarations should be rare, and only
defined internally within a library, such that they are not
exposed at API boundaries.
More generally, do not use concepts or constraints in cases where
you wouldn't use their legacy template equivalents in C++17.
不要手工重新实现任何已有的概念或特征。例如,使用
requires(std::default_initializable<T>)
而不是
requires(requires { T v; })
或类似写法。
新的 concept 声明应当很少见,并且只应在库内部定义,使其不暴露在 API 边界处。更一般地说,不要在 C++17 中你不会使用其传统模板等价物的场景里使用概念或约束。
Do not define concepts that duplicate the function body, or impose requirements that would be insignificant or obvious from reading the body of the code or the resulting error messages. For example, avoid the following:
template <typename T> // Bad - redundant with negligible benefit
concept Addable = std::copyable<T> && requires(T a, T b) { a + b; };
template <Addable T>
T Add(T x, T y, T z) { return x + y + z; }
Instead, prefer to leave code as an ordinary template unless
you can demonstrate that concepts result in significant
improvement for that particular case, such as in the resulting
error messages for a deeply nested or non-obvious
requirement.
不要定义那些重复函数体的概念,或施加那些从函数体或最终错误信息中就能看出、且无关紧要或显而易见的需求。例如,避免如下写法:
相反,除非你能证明对某个特定案例概念带来显著改进(例如:对深度嵌套或不明显需求而言,生成的错误信息明显更好),否则更偏好把代码保留为普通模板。
Concepts should be statically verifiable by the compiler. Do not use any concept whose primary benefits would come from a semantic (or otherwise unenforced) constraint. Requirements that are unenforced at compile time should instead be imposed via other mechanisms such as comments, assertions, or tests.
概念应该能被编译器静态验证。不要使用任何其主要收益来自语义(或其他无法强制执行)约束的概念。那些无法在编译期强制的需求,应该改用注释、断言或测试等其他机制来施加。
C++20 modules C++20 模块
Do not use C++20 Modules.
不要使用 C++20 模块。
C++20 introduces "modules", a new language feature designed as an
alternative to textual inclusion of header files. It introduces three
new keywords to support
this: module, export,
and import.
Modules are a big shift in how C++ is written and compiled, and we are still assessing how they may fit into Google's C++ ecosystem in the future. Furthermore, they are not currently well-supported by our build systems, compilers, and other tooling, and need further exploration as to the best practices when writing and using them.
C++20 引入了“模块”(modules),这是一项新的语言特性,旨在作为头文件文本包含的一种替代方案。它引入了三个新关键字来支持这一机制:module、export 和 import。
模块会显著改变 C++ 的编写与编译方式,我们仍在评估它们未来如何融入 Google 的 C++ 生态系统。此外,目前我们的构建系统、编译器以及其他工具对模块的支持还不完善,因此在编写与使用模块的最佳实践方面仍需要进一步探索。
Coroutines 协程
Only use C++20 coroutines via libraries that have been approved by your project leads.
仅通过你项目负责人批准的库来使用 C++20 协程。
C++20 introduced coroutines: functions that can suspend and resume executing later. They are especially convenient for asynchronous programming, where they can provide substantial improvements over traditional callback-based frameworks.
C++20 引入了 协程: 可以暂停并在之后恢复执行的函数。它们对异步编程尤其方便,相比传统的基于回调的框架,往往能带来显著改进。
Unlike most other programming languages (Kotlin, Rust, TypeScript, etc.), C++ does not provide a concrete implementation of coroutines. Instead, it requires users to implement their own awaitable type (using a promise type) which determines coroutine parameter types, how coroutines are executed, and allows running user-defined code during different stages of their execution.
与大多数其他编程语言(Kotlin、Rust、TypeScript 等)不同,C++ 并未提供协程的具体实现。相反,它要求用户自行实现可等待(awaitable)类型(使用 promise 类型),该类型决定协程的参数类型、协程如何执行,并允许在协程执行的不同阶段运行用户自定义代码。
- Coroutines can be used to implement safe and efficient libraries suited for specific tasks, such as asynchronous programming.
- 协程可用于实现针对特定任务(例如异步编程)的安全且高效的库。
- Coroutines are syntactically almost identical to non-coroutine functions, which can make them substantially more readable than alternatives.
- 协程在语法上与非协程函数几乎相同,相比替代方案通常可读性更好。
- The high degree of customization makes it possible to insert more detailed debugging information into coroutines, compared to alternatives.
- 高度可定制性使得相比替代方案,更容易在协程中插入更细致的调试信息。
- There is no standard coroutine promise type, and each user-defined implementation is likely going to be unique in some aspect.
- 没有标准的协程 promise 类型,每个用户自定义实现很可能在某些方面都是独一无二的。
- Because of load-bearing interactions between the return type, the various customizable hooks in the promise type, and compiler-generated code, coroutine semantics are extremely difficult to deduce from reading user code.
- 由于返回类型、promise 类型中多种可定制钩子以及编译器生成代码之间存在关键的相互作用,仅通过阅读用户代码几乎无法推断出协程的语义。
- The many customizable aspects of coroutines introduce a large number of pitfalls, especially around dangling references and race conditions.
- 协程的众多可定制点带来了大量陷阱,尤其是在悬空引用与竞态条件方面。
In summary, designing a high-quality and interoperable coroutine library requires a large amount of difficult work, careful thought, and extensive documentation.
总之,设计一个高质量且可互操作的协程库需要大量艰难工作、周密思考以及详尽文档。
Use only coroutine libraries that have been approved for project-wide use by your project leads. Do not roll your own promise or awaitable types.
只使用经项目负责人批准、可在整个项目范围内使用的协程库。不要自行实现 promise 或 awaitable 类型。
Boost Boost 库
Use only approved libraries from the Boost library collection.
仅使用 Boost 库集合中经批准的库。
The Boost library collection is a popular collection of peer-reviewed, free, open-source C++ libraries.
Boost 库集合 是一套广受欢迎的、经同行评审的、免费开源的 C++ 库集合。
Boost code is generally very high-quality, is widely portable, and fills many important gaps in the C++ standard library, such as type traits and better binders.
Boost 代码通常质量很高、可移植性好,并弥补了 C++ 标准库中的许多重要空缺,例如类型特征以及更好的 binder(绑定器)。
Some Boost libraries encourage coding practices which can hamper readability, such as metaprogramming and other advanced template techniques, and an excessively "functional" style of programming.
一些 Boost 库鼓励的编码实践可能会损害可读性,例如元编程和其他高级模板技术,以及过度“函数式”的编程风格。
In order to maintain a high level of readability for all contributors who might read and maintain code, we only allow an approved subset of Boost features. Currently, the following libraries are permitted:
为了让所有可能阅读和维护代码的贡献者都能保持较高的可读性,我们只允许使用经批准的 Boost 特性子集。目前允许使用以下库:
-
Call Traits from
boost/call_traits.hpp -
Compressed Pair from
boost/compressed_pair.hpp -
The Boost Graph Library (BGL) from
boost/graph, except serialization (adj_list_serialize.hpp) and parallel/distributed algorithms and data structures (boost/graph/parallel/*andboost/graph/distributed/*). -
Property Map from
boost/property_map, except parallel/distributed property maps (boost/property_map/parallel/*). -
Iterator from
boost/iterator - The part of
Polygon that deals with Voronoi diagram
construction and doesn't depend on the rest of
Polygon:
boost/polygon/voronoi_builder.hpp,boost/polygon/voronoi_diagram.hpp, andboost/polygon/voronoi_geometry_type.hpp -
Bimap from
boost/bimap -
Statistical Distributions and Functions from
boost/math/distributions -
Special Functions from
boost/math/special_functions -
Root Finding & Minimization Functions from
boost/math/tools -
Multi-index from
boost/multi_index -
Heap from
boost/heap - The flat containers from
Container:
boost/container/flat_map, andboost/container/flat_set - Intrusive
from
boost/intrusive. - The
boost/sortlibrary. - Preprocessor
from
boost/preprocessor.
We are actively considering adding other Boost features to the list, so this list may be expanded in the future.
我们正在积极考虑将其他 Boost 特性加入该列表,因此该列表未来可能会扩展。
Disallowed standard library features 禁用的标准库特性
As with Boost, some modern C++ library functionality encourages coding practices that hamper readability — for example by removing checked redundancy (such as type names) that may be helpful to readers, or by encouraging template metaprogramming. Other extensions duplicate functionality available through existing mechanisms, which may lead to confusion and conversion costs.
与 Boost 类似,一些现代 C++ 库功能会鼓励损害可读性的编码实践——例如移除对读者可能有帮助的、可检查的冗余(如类型名),或鼓励模板元编程。还有一些扩展会重复现有机制已提供的功能,从而可能造成困惑并带来迁移成本。
The following C++ standard library features may not be used:
以下 C++ 标准库特性不得使用:
- Compile-time rational numbers
(
<ratio>), because of concerns that it's tied to a more template-heavy interface style. - 编译期有理数(
<ratio>),因为担心它与更“模板重”的接口风格绑定在一起。 - The
<cfenv>and<fenv.h>headers, because many compilers do not support those features reliably. <cfenv>与<fenv.h>头文件,因为许多编译器无法可靠地支持这些特性。- The
<filesystem>header, which does not have sufficient support for testing, and suffers from inherent security vulnerabilities. <filesystem>头文件,因为它对测试的支持不足,并且存在固有的安全漏洞。
Nonstandard Extensions 非标准扩展
Nonstandard extensions to C++ may not be used unless otherwise specified.
除非另有说明,否则不得使用 C++ 的非标准扩展。
Compilers support various extensions that are not part of standard C++. Such
extensions include GCC's __attribute__, intrinsic functions such
as __builtin_prefetch or SIMD, #pragma, inline
assembly, __COUNTER__, __PRETTY_FUNCTION__,
compound statement expressions (e.g., foo = ({ int x; Bar(&x); x
}), variable-length arrays and alloca(), and the
"Elvis
Operator" a?:b.
编译器支持多种不属于标准 C++ 的扩展。这些扩展包括 GCC 的 __attribute__、内建函数(intrinsic)如 __builtin_prefetch 或 SIMD、#pragma、内联汇编、__COUNTER__、__PRETTY_FUNCTION__、复合语句表达式(例如 foo = ({ int x; Bar(&x); x }))、变长数组与 alloca(),以及“Elvis 运算符” a?:b。
- Nonstandard extensions may provide useful features that do not exist in standard C++.
- 非标准扩展可能提供标准 C++ 中不存在的有用特性。
- Important performance guidance to the compiler can only be specified using extensions.
- 某些对编译器的重要性能提示只能通过扩展来表达。
- Nonstandard extensions do not work in all compilers. Use of nonstandard extensions reduces portability of code.
- 非标准扩展并非在所有编译器中都可用。使用非标准扩展会降低代码的可移植性。
- Even if they are supported in all targeted compilers, the extensions are often not well-specified, and there may be subtle behavior differences between compilers.
- 即使目标编译器都支持,这些扩展往往也缺乏良好规范,不同编译器之间可能存在细微的行为差异。
- Nonstandard extensions add features to the language that a reader must know to understand the code.
- 非标准扩展为语言引入了额外特性,读者必须了解这些特性才能读懂代码。
- Nonstandard extensions require additional work to port across architectures.
- 非标准扩展会增加跨架构移植的工作量。
Do not use nonstandard extensions. You may use portability wrappers that are implemented using nonstandard extensions, so long as those wrappers are provided by a designated project-wide portability header.
不要使用非标准扩展。你可以使用基于非标准扩展实现的可移植性封装(wrapper),前提是这些封装由项目指定的、全项目通用的可移植性头文件提供。
Aliases 别名
Public aliases are for the benefit of an API's user, and should be clearly documented.
公共别名是为了方便 API 用户使用,且应当清晰地文档化。
There are several ways to create names that are aliases of other entities:
创建作为其他实体别名的名称有多种方式:
using Bar = Foo; typedef Foo Bar; // But prefer `using` in C++ code. using ::other_namespace::Foo; using enum MyEnumType; // Creates aliases for all enumerators in MyEnumType.
In new code, using is preferable to typedef,
because it provides a more consistent syntax with the rest of C++ and works
with templates.
在新代码中,using 优于 typedef,因为它与 C++ 其他部分的语法更一致,并且能够与模板配合使用。
Like other declarations, aliases declared in a header file are part of that
header's public API unless they're in a function definition, in the private portion of a class,
or in an explicitly-marked internal namespace. Aliases in such areas or in .cc files
are implementation details (because client code can't refer to them), and are not restricted by
this rule.
与其他声明一样,在头文件中声明的别名属于该头文件的公共 API,除非它们位于函数定义中、类的 private 区域中、或明确标记的内部命名空间中。处于这些位置或位于 .cc 文件中的别名是实现细节(因为客户端代码无法引用它们),因此不受本规则限制。
- Aliases can improve readability by simplifying a long or complicated name.
- 别名可以通过简化又长又复杂的名称来提升可读性。
- Aliases can reduce duplication by naming in one place a type used repeatedly in an API, which might make it easier to change the type later.
- 别名可以通过在一处命名 API 中反复使用的类型来减少重复,这可能让未来修改该类型更容易。
- When placed in a header where client code can refer to them, aliases increase the number of entities in that header's API, increasing its complexity.
- 当别名放在客户端可引用的头文件中时,会增加该头文件 API 中实体的数量,从而增加其复杂度。
- Clients can easily rely on unintended details of public aliases, making changes difficult.
- 客户端很容易依赖公共别名中并非有意暴露的细节,从而使变更变得困难。
- It can be tempting to create a public alias that is only intended for use in the implementation, without considering its impact on the API, or on maintainability.
- 人们可能会忍不住创建一个本只打算在实现中使用的公共别名,却没有考虑它对 API 或可维护性的影响。
- Aliases can create risk of name collisions.
- 别名可能带来名称冲突风险。
- Aliases can reduce readability by giving a familiar construct an unfamiliar name.
- 别名可能通过给熟悉的构造起一个不熟悉的名字而降低可读性。
- Type aliases can create an unclear API contract: it is unclear whether the alias is guaranteed to be identical to the type it aliases, to have the same API, or only to be usable in specified narrow ways.
- 类型别名可能导致不清晰的 API 契约:不清楚该别名是否保证始终与其所别名的类型完全相同、是否拥有相同 API,或是否只允许以特定的狭窄方式使用。
Don't put an alias in your public API just to save typing in the implementation; do so only if you intend it to be used by your clients.
不要仅仅为了在实现中少打字就把别名放进公共 API;只有当你确实希望客户端使用它时才这样做。
When defining a public alias, document the intent of the new name, including whether it is guaranteed to always be the same as the type it's currently aliased to, or whether a more limited compatibility is intended. This lets the user know whether they can treat the types as substitutable or whether more specific rules must be followed, and can help the implementation retain some degree of freedom to change the alias.
在定义公共别名时,要文档化新名称的意图,包括:是否保证它始终与当前被别名的类型完全相同,还是只打算提供更有限的兼容性。这能让用户知道他们是否可以把两者视为可替换类型,或必须遵循更具体的规则;也有助于实现保留一定自由度来调整该别名。
Don't put namespace aliases in your public API. (See also Namespaces.)
不要把命名空间别名放进公共 API。(另见 Namespaces。)
For example, these aliases document how they are intended to be used in client code:
例如,下面这些别名清楚地文档化了它们在客户端代码中的预期用法:
namespace mynamespace {
// Used to store field measurements. DataPoint may change from Bar* to some internal type.
// Client code should treat it as an opaque pointer.
using DataPoint = ::foo::Bar*;
// A set of measurements. Just an alias for user convenience.
using TimeSeries = std::unordered_set<DataPoint, std::hash<DataPoint>, DataPointComparator>;
} // namespace mynamespace
These aliases don't document intended use, and half of them aren't meant for client use:
下面这些别名没有文档化预期用法,并且其中一半其实并不打算给客户端使用:
namespace mynamespace {
// Bad: none of these say how they should be used.
using DataPoint = ::foo::Bar*;
using ::std::unordered_set; // Bad: just for local convenience
using ::std::hash; // Bad: just for local convenience
typedef unordered_set<DataPoint, hash<DataPoint>, DataPointComparator> TimeSeries;
} // namespace mynamespace
However, local convenience aliases are fine in function definitions, private
sections of classes, explicitly-marked internal namespaces, and in .cc files:
不过,为了局部便利而定义的别名在函数定义中、类的 private 区域、明确标记的内部命名空间以及 .cc 文件中是可以的:
// In a .cc file using ::foo::Bar;
Switch Statements switch 语句
If not conditional on an enumerated value, switch statements should always
have a default case (in the case of an enumerated value, the
compiler will warn you if any values are not handled). If the default case
should never execute, treat this as an error. For example:
如果 switch 不是基于枚举值进行分支,则应当始终包含一个 default 分支(若基于枚举值,编译器会在有枚举值未被处理时发出警告)。如果 default 分支按设计永远不应执行,应将其视为错误。例如:
switch (var) {
case 0: {
...
break;
}
case 1: {
...
break;
}
default: {
LOG(FATAL) << "Invalid value in switch statement: " << var;
}
}
Fall-through from one case label to another must be annotated using the
[[fallthrough]]; attribute. [[fallthrough]]; should
be placed at a point of execution where a fall-through to the next case label
occurs. A common exception is consecutive case labels without intervening code,
in which case no annotation is needed.
从一个 case 标签“贯穿”(fall-through)到另一个 case 标签必须使用 [[fallthrough]]; 属性进行标注。[[fallthrough]]; 应放在实际发生贯穿到下一个 case 标签的执行点上。一个常见例外是连续的 case 标签之间没有任何代码,此时无需标注。
switch (x) {
case 41: // No annotation needed here.
case 43:
if (dont_be_picky) {
// Use this instead of or along with annotations in comments.
[[fallthrough]];
} else {
CloseButNoCigar();
break;
}
case 42:
DoSomethingSpecial();
[[fallthrough]];
default:
DoSomethingGeneric();
break;
}
Inclusive Language 包容性语言
In all code, including naming and comments, use inclusive language and avoid terms that other programmers might find disrespectful or offensive (such as "master" and "slave", "blacklist" and "whitelist", or "redline"), even if the terms also have an ostensibly neutral meaning. Similarly, use gender-neutral language unless you're referring to a specific person (and using their pronouns). For example, use "they"/"them"/"their" for people of unspecified gender (even when singular), and "it"/"its" for software, computers, and other things that aren't people.
在所有代码中(包括命名与注释),使用包容性语言,避免使用其他程序员可能认为不尊重或冒犯的术语(例如 “master”/“slave”、“blacklist”/“whitelist” 或 “redline”),即使这些术语表面上也可能有中性的含义。同样,除非你在指代某个特定的人(并使用其代词),否则应使用性别中立语言。例如,对于性别不明确的人使用 “they”/“them”/“their”(即使在单数语境),对于软件、计算机以及其他非人的事物使用 “it”/“its”。
Naming 命名
The most important consistency rules are those that govern naming. The style of a name immediately informs us what sort of thing the named entity is: a type, a variable, a function, a constant, a macro, etc., without requiring us to search for the declaration of that entity. The pattern-matching engine in our brains relies a great deal on these naming rules.
最重要的一致性规则是命名规则。一个名字的风格会立刻告诉我们它所命名的实体属于哪一类:类型、变量、函数、常量、宏等等,而不需要我们去查找该实体的声明。我们大脑中的模式匹配机制在很大程度上依赖这些命名规则。
Style rules about naming are pretty arbitrary, but we feel that consistency is more important than individual preferences in this area, so regardless of whether you find them sensible or not, the rules are the rules.
关于命名的风格规则相当武断,但我们认为在这一领域里一致性比个人偏好更重要。因此,无论你觉得这些规则是否合理,规则就是规则。
For the purposes of the naming rules below, a "word" is anything that you would write in English without internal spaces. Either words are all lowercase, with underscores between words ("snake_case"), or words are mixed case with the first letter of each word capitalized ("camelCase" or "PascalCase").
在下述命名规则中,“word”(单词)指的是你在英文中书写且内部不含空格的任何词。单词要么全部小写并用下划线分隔(“snake_case”),要么使用大小写混合、每个单词首字母大写(“camelCase” 或 “PascalCase”)。
Choosing Names 选择名称
Give things names that make their purpose or intent understandable to a new reader, even someone on a different team than the owners. Do not worry about saving horizontal space as it is far more important to make your code immediately understandable by a new reader.
给事物起能让新读者理解其目的或意图的名字,即使读者来自不同于所有者的团队。不要担心节省横向空间;让新读者能立刻读懂你的代码要重要得多。
Consider the context in which the name will be used. A name should be descriptive even if it is used far from the code that makes it available for use. However, a name should not distract the reader by repeating information that's present in the immediate context. Generally, this means that descriptiveness should be proportional to the name's scope of visibility. A free function declared in a header should probably mention the header's library, while a local variable probably shouldn't explain what function it's within.
考虑名字将被使用的上下文。即使某个名字在远离其定义处的地方被使用,它也应具有描述性。然而,名字不应通过重复当前上下文中已存在的信息来分散读者注意力。一般来说,这意味着描述性应与名字的可见范围成比例:在头文件中声明的自由函数可能需要提及该头文件所属的库,而局部变量通常不必说明它位于哪个函数之中。
Minimize the use of abbreviations that would likely be unknown to someone
outside your project (especially acronyms and initialisms). Do not abbreviate by
deleting letters within a word. When an abbreviation is used, prefer to
capitalize it as a single "word", e.g., StartRpc() rather than
StartRPC(). As a rule of thumb, an abbreviation is probably OK
if it's listed in
Wikipedia. Note that certain universally-known abbreviations are OK, such as
i for a loop index and T for a template
parameter.
尽量减少使用项目外的人可能不熟悉的缩写(尤其是首字母缩略词)。不要通过删除单词内部字母来构造缩写。使用缩写时,倾向把它当作一个“word”来首字母大写,例如使用 StartRpc() 而不是 StartRPC()。经验法则是:如果缩写能在 Wikipedia 上查到,它大概率是可以接受的。注意,一些众所周知的缩写也是可以的,例如用 i 表示循环索引、用 T 表示模板参数。
The names you see most frequently are not like most names; a small number of
"vocabulary" names are reused so widely that they are always in context. These
names tend to be short or even abbreviated and their full meaning comes from
explicit long-form documentation rather than from just comments on their
definition and the words within the names. For example, absl::Status
has a dedicated
page
in a devguide,
documenting its proper use. You probably won't define new vocabulary names very
often, but if you do, get
additional design review to make sure the chosen names work well when
used widely.
你最常见到的一些名字并不像大多数名字那样:少数“词汇”(vocabulary)名字被如此广泛地复用,以至于它们几乎总是在上下文中出现。这些名字通常很短、甚至是缩写;它们完整含义更多来自专门的长篇文档,而不是仅靠定义处的注释与名字中各个单词本身。例如,absl::Status 在开发指南中有一个专门的页面来说明正确用法。你可能不会经常定义新的词汇名字,但如果你需要这么做,应当获得额外的设计评审,以确保所选名字在被广泛使用时仍能很好地工作。
class MyClass {
public:
int CountFooErrors(const std::vector<Foo>& foos) {
int n = 0; // Clear meaning given limited scope and context
for (const auto& foo : foos) {
...
++n;
}
return n;
}
// Function comment doesn't need to explain that this returns non-OK on
// failure as that is implied by the `absl::Status` return type, but it
// might document behavior for some specific codes.
absl::Status DoSomethingImportant() {
std::string fqdn = ...; // Well-known abbreviation for Fully Qualified Domain Name
return absl::OkStatus();
}
private:
const int kMaxAllowedConnections = ...; // Clear meaning within context
};
class MyClass {
public:
int CountFooErrors(const std::vector<Foo>& foos) {
int total_number_of_foo_errors = 0; // Overly verbose given limited scope and context
for (int foo_index = 0; foo_index < foos.size(); ++foo_index) { // Use idiomatic `i`
...
++total_number_of_foo_errors;
}
return total_number_of_foo_errors;
}
// A return type with a generic name is unclear without widespread education.
Result DoSomethingImportant() {
int cstmr_id = ...; // Deletes internal letters
}
private:
const int kNum = ...; // Unclear meaning within broad scope
};
File Names 文件名
Filenames should be all lowercase and can include
underscores (_) or dashes (-).
Follow the convention that your
project uses. If there is no consistent
local pattern to follow, prefer "_".
文件名应全部小写,并且可以包含下划线(_)或短横线(-)。遵循你项目使用的约定。如果没有一致的本地模式可遵循,优先使用 “_”。
Examples of acceptable file names:
可接受的文件名示例:
my_useful_class.ccmy-useful-class.ccmyusefulclass.ccmyusefulclass_test.cc // _unittest and _regtest are deprecated.
C++ files should have a .cc filename extension, and header files
should have a .h extension. Files that rely on being textually included at specific points
should end in .inc (see also the section on
self-contained headers).
C++ 源文件应使用 .cc 扩展名,头文件应使用 .h 扩展名。依赖在特定位置被文本包含的文件应以 .inc 结尾(另见 self-contained headers 一节)。
Do not use filenames that already exist in
/usr/include, such as db.h.
不要使用在 /usr/include 中已存在的文件名,例如 db.h。
In general, make your filenames very specific. For
example, use http_server_logs.h rather than
logs.h. A very common case is to have a pair
of files called, e.g., foo_bar.h and
foo_bar.cc, defining a class called
FooBar.
一般来说,文件名要尽量具体。例如,使用 http_server_logs.h 而不是 logs.h。一个非常常见的情况是:一对文件名为 foo_bar.h 与 foo_bar.cc,它们定义了一个名为 FooBar 的类。
Type Names 类型名
Type names start with a capital letter and have a capital
letter for each new word, with no underscores:
MyExcitingClass, MyExcitingEnum.
类型名以大写字母开头,每个新单词的首字母也要大写,并且不使用下划线:例如 MyExcitingClass、MyExcitingEnum。
The names of all types — classes, structs, type aliases, enums, and type template parameters — have the same naming convention. Type names should start with a capital letter and have a capital letter for each new word. No underscores. For example:
所有类型(类、结构体、类型别名、枚举,以及类型模板参数)的命名约定相同。类型名应以大写字母开头,并且每个新单词的首字母大写;不使用下划线。例如:
// classes and structs
class UrlTable { ...
class UrlTableTester { ...
struct UrlTableProperties { ...
// typedefs
typedef hash_map<UrlTableProperties*, std::string> PropertiesMap;
// using aliases
using PropertiesMap = hash_map<UrlTableProperties*, std::string>;
// enums
enum class UrlTableError { ...
Concept Names 概念名
Concept names follow the same rules as type names.
概念名遵循与 类型名 相同的规则。
Variable Names 变量名
The names of variables (including function parameters) and data members are
snake_case (all lowercase, with underscores between words). Data members of classes
(but not structs) additionally have trailing underscores. For instance:
a_local_variable, a_struct_data_member,
a_class_data_member_.
变量名(包括函数参数)以及数据成员使用 snake_case(全部小写,单词间用下划线分隔)。类的数据成员(但不是结构体)还需要在末尾加下划线。例如:a_local_variable、a_struct_data_member、a_class_data_member_。
Common Variable names 常见变量名
For example:
例如:
std::string table_name; // OK - snake_case.
std::string tableName; // Bad - mixed case.
Class Data Members 类数据成员
Data members of classes, both static and non-static, are named like ordinary nonmember variables, but with a trailing underscore. The exception to this is static constant class members, which should follow the rules for naming constants.
类的数据成员(无论静态还是非静态)像普通的非成员变量一样命名,但末尾需要加下划线。唯一例外是静态常量类成员,它们应遵循常量命名规则。
class TableInfo {
public:
...
static const int kTableVersion = 3; // OK - constant naming.
...
private:
std::string table_name_; // OK - underscore at end.
static Pool<TableInfo>* pool_; // OK.
};
Struct Data Members 结构体数据成员
Data members of structs, both static and non-static, are named like ordinary nonmember variables. They do not have the trailing underscores that data members in classes have.
结构体的数据成员(无论静态还是非静态)像普通的非成员变量一样命名。它们不需要像类数据成员那样在末尾加下划线。
struct UrlTableProperties {
std::string name;
int num_entries;
static Pool<UrlTableProperties>* pool;
};
See Structs vs. Classes for a discussion of when to use a struct versus a class.
关于何时使用结构体而非类,请参见 Structs vs. Classes 一节。
Constant Names 常量名
Variables declared constexpr or const, and whose value is fixed for
the duration of the program, are named with a leading "k" followed
by mixed case. Underscores can be used as separators in the rare cases
where capitalization cannot be used for separation. For example:
被声明为 constexpr 或 const,且其值在程序生命周期内固定不变的变量,使用前缀 “k” 加上驼峰(mixed case)命名。在少数无法用大小写来分隔的情况下,可以用下划线作为分隔符。例如:
const int kDaysInAWeek = 7; const int kAndroid8_0_0 = 24; // Android 8.0.0
All such variables with static storage duration (i.e., statics and globals, see Storage Duration for details) should be named this way, including those that are static constant class data members and those in templates where different instantiations of the template may have different values. This convention is optional for variables of other storage classes, e.g., automatic variables; otherwise the usual variable naming rules apply. For example:
所有具有静态存储期的此类变量(即静态变量与全局变量,详情参见 Storage Duration)都应采用这种命名方式,包括静态常量类数据成员,以及模板中不同实例化可能拥有不同值的变量。对于其他存储类别的变量(例如自动变量),该约定是可选的;否则仍使用通常的变量命名规则。例如:
void ComputeFoo(absl::string_view suffix) {
// Either of these is acceptable.
const absl::string_view kPrefix = "prefix";
const absl::string_view prefix = "prefix";
...
}
void ComputeFoo(absl::string_view suffix) {
// Bad - different invocations of ComputeFoo give kCombined different values.
const std::string kCombined = absl::StrCat(kPrefix, suffix);
...
}
Function Names 函数名
Ordinarily, functions follow PascalCase: start with a capital letter and have a capital letter for each new word.
通常,函数名遵循 PascalCase:以大写字母开头,并且每个新单词的首字母大写。
AddTableEntry() DeleteUrl() OpenFileOrDie()
The same naming rule applies to class- and namespace-scope constants that are exposed as part of an API and that are intended to look like functions, because the fact that they're objects rather than functions is an unimportant implementation detail.
同样的命名规则也适用于那些作为 API 的一部分对外暴露、并且意图看起来像函数的类作用域或命名空间作用域常量;因为它们是对象而非函数这一点只是无关紧要的实现细节。
Accessors and mutators (get and set functions) may be named like variables,
in snake_case. These often correspond to actual member variables,
but this is not required. For example, int count() and
void set_count(int count).
访问器和修改器(getter/setter)可以像变量一样用 snake_case 命名。它们通常对应实际的成员变量,但并非必须如此。例如:int count() 与 void set_count(int count)。
Namespace Names 命名空间名
Namespace names are snake_case (all lowercase, with underscores
between words).
命名空间名使用 snake_case(全部小写,单词间用下划线分隔)。
When choosing names for namespaces, note that names must be fully qualified when used in a header outside the namespace, because unqualified Aliases are generally banned.
在为命名空间选择名称时,请注意:当在该命名空间之外的头文件中使用这些名称时,必须写全限定名,因为不带限定的别名通常是被禁止的。
Top-level namespaces must be globally unique and recognizable, so each one should be owned by a single project or team, with a name based on the name of that project or team. Usually, all code in the namespace should be under one or more directories with the same name as the namespace.
顶层命名空间必须全局唯一且易于识别,因此每个顶层命名空间应由单个项目或团队拥有,其名称应基于该项目或团队的名称。通常,命名空间中的所有代码应位于一个或多个与该命名空间同名的目录下。
Nested namespaces should avoid the names of well-known top-level namespaces,
especially std and absl, because in C++, nested
namespaces do not protect from collisions with names in other namespaces
(see TotW #130).
嵌套命名空间应避免使用知名顶层命名空间的名字,尤其是 std 和 absl,因为在 C++ 中,嵌套命名空间并不能防止与其他命名空间中的名字发生冲突(参见 TotW #130)。
Enumerator Names 枚举项名称
Enumerators (for both scoped and unscoped enums) should be named like
constants, not like
macros. That is, use kEnumName not
ENUM_NAME.
枚举项(无论是限定作用域还是非限定作用域的枚举)应像常量那样命名,而不是像宏那样命名。也就是说,使用 kEnumName,而不是 ENUM_NAME。
enum class UrlTableError {
kOk = 0,
kOutOfMemory,
kMalformedInput,
};
enum class AlternateUrlTableError {
OK = 0,
OUT_OF_MEMORY = 1,
MALFORMED_INPUT = 2,
};
Until January 2009, the style was to name enum values like macros. This caused problems with name collisions between enum values and macros. Hence, the change to prefer constant-style naming was put in place. New code should use constant-style naming.
在 2009 年 1 月之前,风格是把枚举值像宏一样命名。这导致枚举值与宏之间发生名称冲突的问题。因此,后来改为偏好常量风格的命名。新代码应使用常量风格命名。
Template Parameter Names 模板参数名称
Template parameters should follow the naming style for their category: type template parameters should follow the rules for naming types, and non-type template parameters should follow the rules for naming variables or constants.
模板参数应遵循其类别对应的命名风格:类型模板参数应遵循类型的命名规则;非类型模板参数应遵循变量或常量的命名规则。
Macro Names 宏名称
You're not really going to
define a macro, are you? If you do, they're like this:
MY_MACRO_THAT_SCARES_SMALL_CHILDREN_AND_ADULTS_ALIKE.
你不会真的要定义宏吧?如果你非要这么做,它们应该长这样:
MY_MACRO_THAT_SCARES_SMALL_CHILDREN_AND_ADULTS_ALIKE。
Please see the description of macros; in general macros should not be used. However, if they are absolutely needed, then they should be named with all capitals and underscores, and with a project-specific prefix.
请参阅关于宏的说明;一般而言,宏不应被使用。但如果确实绝对需要,那么它们应使用全大写与下划线命名,并带有项目特定的前缀。
#define MYPROJECT_ROUND(x) ...
Aliases 别名
The name for an alias follows the same principles as any other new name, applied in the context where the alias is defined rather than where the original name appears.
别名的命名遵循与其他新名称相同的原则:应在别名被定义的上下文中应用这些原则,而不是在原始名称出现的上下文中。
Exceptions to Naming Rules 命名规则的例外
If you are naming something that is analogous to an existing C or C++ entity (or a Rust entity via interop), then you can follow the existing naming convention scheme.
如果你要命名的东西与现有的 C 或 C++ 实体(或通过互操作的 Rust 实体)类似,那么你可以遵循该现有实体的命名约定。
bigopen()- function name, follows form of
open() - 函数名,遵循
open()的形式 uinttypedeftypedef(类型别名)bigposstructorclass, follows form ofposstruct或class,遵循pos的形式sparse_hash_map- STL-like entity; follows STL naming conventions
- 类 STL 实体;遵循 STL 命名约定
LONGLONG_MAX- a constant, as in
INT_MAX - 常量,如
INT_MAX
Comments 注释
Comments are absolutely vital to keeping our code readable. The following rules describe what you should comment and where. But remember: while comments are very important, the best code is self-documenting. Giving sensible names to types and variables is much better than using obscure names that you must then explain through comments.
注释对于保持代码可读性至关重要。下面的规则说明你应该注释什么以及在哪里注释。但请记住:尽管注释很重要,最好的代码仍是自解释的。给类型和变量起合理的名字,远胜于使用晦涩名字然后不得不靠注释来解释。
When writing your comments, write for your audience: the next contributor who will need to understand your code. Be generous — the next one may be you!
写注释时要面向你的受众:下一个需要理解你代码的贡献者。尽量写得慷慨一些——下一个人也可能就是你自己!
Comment Style 注释风格
Use either the // or /* */
syntax, as long as you are consistent.
只要保持一致,你可以使用 // 或 /* */ 语法。
While either syntax is acceptable, // is
much more common. Be consistent with how you
comment and what style you use where.
两种语法都可以,但 // 更为常见(多得多)。要在注释方式以及在何处使用何种风格上保持一致。
File Comments 文件注释
Start each file with license boilerplate.
每个文件都应以许可证样板(license boilerplate)开头。
If a source file (such as a .h file) declares multiple user-facing abstractions
(common functions, related classes, etc.), include a comment describing the collection of those
abstractions. Include enough detail for future authors to know what does not fit there. However,
the detailed documentation about individual abstractions belongs with those abstractions, not at the
file level.
如果一个源文件(例如 .h 文件)声明了多个面向用户的抽象(常用函数、相关类等),应添加注释来描述这些抽象的集合。要包含足够细节,使未来作者知道哪些内容不应该放在这里。不过,关于单个抽象的详细文档应与该抽象放在一起,而不是放在文件级别。
For instance, if you write a file comment for frobber.h, you do not need
to include a file comment in frobber.cc or
frobber_test.cc. On the other hand, if you write a collection of classes in
registered_objects.cc that has no associated header file, you must include a file
comment in registered_objects.cc.
例如,如果你为 frobber.h 写了文件注释,那么你不需要在 frobber.cc 或 frobber_test.cc 中再写一份文件注释。另一方面,如果你在 registered_objects.cc 中写了一组类而没有对应的头文件,那么你必须在 registered_objects.cc 中包含文件注释。
Legal Notice and Author Line 法律声明与作者行
Every file should contain license boilerplate. Choose the appropriate boilerplate for the license used by the project (for example, Apache 2.0, BSD, LGPL, GPL).
每个文件都应包含许可证样板。根据项目所使用的许可证选择合适的样板(例如 Apache 2.0、BSD、LGPL、GPL)。
If you make significant changes to a file with an author line, consider deleting the author line. New files should usually not contain copyright notice or author line.
如果你对带有作者行的文件做了重大修改,考虑删除作者行。新文件通常不应包含版权声明或作者行。
Struct and Class Comments 结构体与类注释
Every non-obvious class or struct declaration should have an accompanying comment that describes what it is for and how it should be used.
每个不那么显而易见的类或结构体声明都应配有注释,说明它的用途以及应如何使用。
// Iterates over the contents of a GargantuanTable.
// Example:
// std::unique_ptr<GargantuanTableIterator> iter = table->NewIterator();
// for (iter->Seek("foo"); !iter->done(); iter->Next()) {
// process(iter->key(), iter->value());
// }
class GargantuanTableIterator {
...
};
Class Comments 类注释
The class comment should provide the reader with enough information to know how and when to use the class, as well as any additional considerations necessary to correctly use the class. Document the synchronization assumptions the class makes, if any. If an instance of the class can be accessed by multiple threads, take extra care to document the rules and invariants surrounding multithreaded use.
类注释应为读者提供足够信息,使其了解如何以及何时使用该类,并包含正确使用该类所需的任何额外注意事项。如果该类有同步方面的假设,应将其记录下来。如果该类的实例可能被多个线程访问,应格外谨慎地记录与多线程使用相关的规则与不变量。
The class comment is often a good place for a small example code snippet demonstrating a simple and focused usage of the class.
类注释通常也是放置一小段示例代码的好地方,用于演示该类简单且聚焦的用法。
When sufficiently separated (e.g., .h and .cc
files), comments describing the use of the class should go together with its
interface definition; comments about the class operation and implementation
should accompany the implementation of the class's methods.
当接口与实现充分分离时(例如分别位于 .h 与 .cc 文件中),描述类如何使用的注释应与其接口定义放在一起;而关于类的运行方式与实现细节的注释应伴随该类方法的实现。
Function Comments 函数注释
Declaration comments describe use of the function (when it is non-obvious); comments at the definition of a function describe operation.
函数声明处的注释用于描述函数的用法(当用法并不明显时);函数定义处的注释用于描述其运行方式(operation)。
Function Declarations 函数声明
Almost every function declaration should have comments immediately
preceding it that describe what the function does and how to use
it. These comments may be omitted only if the function is simple and
obvious (e.g., simple accessors for obvious properties of the class).
Private methods and functions declared in .cc files are not exempt.
Function comments should be written with an implied subject of
This function and should start with the verb phrase; for example,
"Opens the file", rather than "Open the file". In general, these comments do not
describe how the function performs its task. Instead, that should be
left to comments in the function definition.
几乎每个函数声明之前都应紧跟注释,说明该函数做什么以及如何使用。只有当函数简单且显而易见时(例如用于类中显而易见属性的简单访问器)才可以省略这些注释。.cc 文件中声明的私有方法和函数也不例外。函数注释应以隐含主语 This function 来撰写,并以动词短语开头;例如写 “Opens the file”,而不是 “Open the file”。一般而言,这些注释不应描述函数如何完成任务;那应留给函数定义处的注释来说明。
Types of things to mention in comments at the function declaration:
在函数声明的注释中可以提及的内容类型:
- What the inputs and outputs are. If function argument names are provided in `backticks`, then code-indexing tools may be able to present the documentation better.
- 输入与输出是什么。如果函数参数名用 `backticks` 标出,代码索引工具可能能更好地呈现文档。
- For class member functions: whether the object remembers reference or pointer arguments beyond the duration of the method call. This is quite common for pointer/reference arguments to constructors.
- 对于类成员函数:对象是否会在方法调用结束后仍“记住”引用或指针参数。这在构造函数的指针/引用参数中很常见。
- For each pointer argument, whether it is allowed to be null and what happens if it is.
- 对于每个指针参数:是否允许为空,以及为空时会发生什么。
- For each output or input/output argument, what happens to any state that argument is in (e.g., is the state appended to or overwritten?).
- 对于每个输出或输入/输出参数:该参数当前所处的状态会如何变化(例如:状态是被追加还是被覆盖?)。
- If there are any performance implications of how a function is used.
- 函数的使用方式是否会带来任何性能影响。
Here is an example:
下面是一个例子:
// Returns an iterator for this table, positioned at the first entry // lexically greater than or equal to `start_word`. If there is no // such entry, returns a null pointer. The client must not use the // iterator after the underlying GargantuanTable has been destroyed. // // This method is equivalent to: // std::unique_ptr<Iterator> iter = table->NewIterator(); // iter->Seek(start_word); // return iter; std::unique_ptr<Iterator> GetIterator(absl::string_view start_word) const;
However, do not be unnecessarily verbose or state the completely obvious.
不过,不要写得不必要地冗长,也不要陈述完全显而易见的内容。
When documenting function overrides, focus on the specifics of the override itself, rather than repeating the comment from the overridden function. In many of these cases, the override needs no additional documentation and thus no comment is required.
在为函数重写(override)撰写文档时,应聚焦于该重写本身的特性,而不是重复被重写函数的注释。在很多情况下,重写并不需要额外文档,因此也不需要注释。
When commenting constructors and destructors, remember that the person reading your code knows what constructors and destructors are for, so comments that just say something like "destroys this object" are not useful. Document what constructors do with their arguments (for example, if they take ownership of pointers), and what cleanup the destructor does. If this is trivial, just skip the comment. It is quite common for destructors not to have a header comment.
在注释构造函数与析构函数时,请记住阅读你代码的人知道它们的用途,因此仅仅写类似“销毁这个对象”之类的话并没有帮助。应记录构造函数如何处理其参数(例如是否接管指针的所有权),以及析构函数会做哪些清理工作。如果这些内容很琐碎,就直接省略注释。析构函数没有头部注释也是相当常见的。
Function Definitions 函数定义
If there is anything tricky about how a function does its job, the function definition should have an explanatory comment. For example, in the definition comment you might describe any coding tricks you use, give an overview of the steps you go through, or explain why you chose to implement the function in the way you did rather than using a viable alternative. For instance, you might mention why it must acquire a lock for the first half of the function but why it is not needed for the second half.
如果函数如何完成工作存在任何棘手之处,那么函数定义处应有解释性注释。例如,你可以在定义处的注释里描述所用的编码技巧、概述执行步骤,或解释你为何选择这种实现方式而不是某个可行替代方案。比如,你可以说明为什么函数前半段必须获取锁,以及为什么后半段不需要。
Note you should not just repeat the comments
given with the function declaration, in the
.h file or wherever. It's okay to
recapitulate briefly what the function does, but the
focus of the comments should be on how it does it.
注意,你不应只是重复 .h 文件或其他位置的函数声明注释。简要回顾函数做什么是可以的,但注释重点应放在它如何做到。
Variable Comments 变量注释
In general the actual name of the variable should be descriptive enough to give a good idea of what the variable is used for. In certain cases, more comments are required.
一般来说,变量名本身就应足够具有描述性,让人对该变量的用途有一个良好的认识。在某些情况下,还需要更多注释。
Class Data Members 类数据成员
The purpose of each class data member (also called an instance
variable or member variable) must be clear. If there are any
invariants (special values, relationships between members, lifetime
requirements) not clearly expressed by the type and name, they must be
commented. However, if the type and name suffice (int
num_events_;), no comment is needed.
每个类数据成员(也称实例变量或成员变量)的用途必须清晰。如果存在任何无法从类型与名称清楚表达的不变量(特殊值、成员间关系、生命周期要求),就必须加以注释。反之,如果类型与名称已经足够(例如 int num_events_;),则不需要注释。
In particular, add comments to describe the existence and meaning of sentinel values, such as nullptr or -1, when they are not obvious. For example:
尤其是,当哨兵值(例如 nullptr 或 -1)的存在及其含义并不明显时,应添加注释加以说明。例如:
private: // Used to bounds-check table accesses. -1 means // that we don't yet know how many entries the table has. int num_total_entries_;
Global Variables 全局变量
All global variables should have a comment describing what they are, what they are used for, and (if unclear) why they need to be global. For example:
所有全局变量都应有注释,说明它们是什么、用来做什么,以及(如果不清楚的话)为什么它们需要是全局的。例如:
// The total number of test cases that we run through in this regression test. const int kNumTestCases = 6;
Implementation Comments 实现注释
In your implementation you should have comments in tricky, non-obvious, interesting, or important parts of your code.
在实现中,你应在棘手、非显而易见、有趣或重要的代码部分添加注释。
Explanatory Comments 解释性注释
Tricky or complicated code blocks should have comments before them.
棘手或复杂的代码块应在其前面添加注释。
Function Argument Comments 函数参数注释
When the meaning of a function argument is nonobvious, consider one of the following remedies:
当函数参数的含义并不明显时,可以考虑以下补救措施之一:
- If the argument is a literal constant, and the same constant is used in multiple function calls in a way that tacitly assumes they're the same, you should use a named constant to make that constraint explicit, and to guarantee that it holds.
- 如果参数是字面量常量,并且同一个常量在多个函数调用中使用且暗含“它们应当相同”的假设,你应使用命名常量把该约束显式化,并保证其成立。
- Consider changing the function signature to replace a
boolargument with anenumargument. This will make the argument values self-describing. - 考虑修改函数签名:用
enum参数替代bool参数,使参数值具备自描述性。 - For functions that have several configuration options, consider defining a single class or struct to hold all the options , and pass an instance of that. This approach has several advantages. Options are referenced by name at the call site, which clarifies their meaning. It also reduces function argument count, which makes function calls easier to read and write. As an added benefit, you don't have to change call sites when you add another option.
- 对于有多个配置选项的函数,考虑定义一个类或结构体来承载所有选项,并传入其实例。这种方式有多项优势:调用处可以按名称引用选项,从而澄清含义;也能减少函数参数数量,让调用更易读、更易写;并且新增选项时无需修改既有调用点。
- Replace large or complex nested expressions with named variables.
- 用命名变量替换又大又复杂的嵌套表达式。
- As a last resort, use comments to clarify argument meanings at the call site.
- 最后的手段:在调用点使用注释来澄清参数含义。
Consider the following example:
考虑下面的例子:
// What are these arguments? const DecimalNumber product = CalculateProduct(values, 7, false, nullptr);
versus:
对比:
ProductOptions options;
options.set_precision_decimals(7);
options.set_use_cache(ProductOptions::kDontUseCache);
const DecimalNumber product =
CalculateProduct(values, options, /*completion_callback=*/nullptr);
Don'ts 禁止事项
Do not state the obvious. In particular, don't literally describe what code does, unless the behavior is nonobvious to a reader who understands C++ well. Instead, provide higher-level comments that describe why the code does what it does, or make the code self-describing.
不要陈述显而易见的内容。尤其是,除非某段行为对于理解良好的 C++ 读者来说并不明显,否则不要逐字描述代码在做什么。相反,应提供更高层次的注释,说明代码为何这样做,或者让代码本身具备自解释性。
Compare this: 对比如下:// Find the element in the vector. <-- Bad: obvious!
if (std::find(v.begin(), v.end(), element) != v.end()) {
Process(element);
}
To this:
再对比如下:
// Process "element" unless it was already processed.
if (std::find(v.begin(), v.end(), element) != v.end()) {
Process(element);
}
Self-describing code doesn't need a comment. The comment from the example above would be obvious:
自解释的代码不需要注释。上面示例中的注释如果写在这里,就会显得显而易见:
if (!IsAlreadyProcessed(element)) {
Process(element);
}
Punctuation, Spelling, and Grammar 标点、拼写与语法
Pay attention to punctuation, spelling, and grammar; it is easier to read well-written comments than badly written ones.
注意标点、拼写与语法;写得好的注释比写得差的注释更易阅读。
Comments should be as readable as narrative text, with proper capitalization and punctuation. In many cases, complete sentences are more readable than sentence fragments. Shorter comments, such as comments at the end of a line of code, can sometimes be less formal, but you should be consistent with your style.
注释应像叙述性文本一样易读,具备正确的大小写与标点。在许多情况下,完整句子比句子片段更易读。较短的注释(例如代码行尾的注释)有时可以不那么正式,但你应当在风格上保持一致。
Although it can be frustrating to have a code reviewer point out that you are using a comma when you should be using a semicolon, it is very important that source code maintain a high level of clarity and readability. Proper punctuation, spelling, and grammar help with that goal.
尽管代码审查者指出你该用分号却用了逗号可能令人沮丧,但让源代码保持高度清晰与可读性非常重要。正确的标点、拼写与语法有助于实现这一目标。
TODO Comments TODO 注释
Use TODO comments for code that is temporary,
a short-term solution, or good-enough but not perfect.
对临时性的代码、短期解决方案,或“够用但不完美”的实现使用 TODO 注释。
TODOs should include the string
TODO in all caps, followed by the bug ID, name, e-mail address, or other identifier
of the person or issue with the best context
about the problem referenced by the TODO.
Recommended styles are (in order of preference):
TODO 应包含全大写字符串 TODO,后接 bug ID、姓名、电子邮件地址,或其他可标识具有最佳上下文的人员或问题的标识符,以说明该 TODO 所指问题。
推荐样式如下(按偏好顺序):
// TODO: bug 12345678 - Remove this after the 2047q4 compatibility window expires. // TODO: example.com/my-design-doc - Manually fix up this code the next time it's touched. // TODO(bug 12345678): Update this list after the Foo service is turned down. // TODO(John): Use a "\*" here for concatenation operator.
If your TODO is of the form "At a future
date do something" make sure that you either include a
very specific date ("Fix by November 2005") or a very
specific event ("Remove this code when all clients can
handle XML responses.").
如果你的 TODO 形如“将来某个时间做某事”,请确保要么包含非常明确的日期(“在 2005 年 11 月前修复”),要么包含非常明确的事件(“当所有客户端都能处理 XML 响应时删除这段代码。”)。
Formatting 格式
Coding style and formatting are pretty arbitrary, but a project is much easier to follow if everyone uses the same style. Individuals may not agree with every aspect of the formatting rules, and some of the rules may take some getting used to, but it is important that all project contributors follow the style rules so that they can all read and understand everyone's code easily.
编码风格与格式化在很大程度上是任意的,但如果每个人都使用同一种风格,项目就会更容易理解。个人未必认同每一条格式规则,某些规则也可能需要时间适应,但所有项目贡献者都遵循这些风格规则非常重要,这样大家才能轻松阅读并理解彼此的代码。
To help you format code correctly, we've created a settings file for emacs.
为帮助你正确格式化代码,我们提供了一个用于 emacs 的 配置文件。
Line Length 行长度
Each line of text in your code should be at most 80 characters long.
代码中的每一行文本最长应为 80 个字符。
We recognize that this rule is controversial, but so much existing code already adheres to it, and we feel that consistency is important.
我们知道这条规则存在争议,但已有大量现存代码遵循它,我们认为一致性很重要。
Those who favor this rule argue that it is rude to force them to resize their windows and there is no need for anything longer. Some folks are used to having several code windows side-by-side, and thus don't have room to widen their windows in any case. People set up their work environment assuming a particular maximum window width, and 80 columns has been the traditional standard. Why change it?
支持这条规则的人认为,强迫他们调整窗口大小是不礼貌的,而且没有必要写得更长。有些人习惯把多个代码窗口并排放置,因此无论如何也没有空间把窗口加宽。人们在设置工作环境时会假定一个特定的最大窗口宽度,而 80 列一直是传统标准。为什么要改变它?
Proponents of change argue that a wider line can make code more readable. The 80-column limit is an hidebound throwback to 1960s mainframes; modern equipment has wide screens that can easily show longer lines.
支持改变的人认为,更宽的行可以让代码更易读。80 列限制是对 1960 年代大型机时代的保守沿袭;现代设备的宽屏很容易显示更长的行。
80 characters is the maximum.
最多 80 个字符。
A line may exceed 80 characters if it is
以下情况允许一行超过 80 个字符:
- a comment line which is not feasible to split without harming readability, ease of cut and paste or auto-linking -- e.g., if a line contains an example command or a literal URL longer than 80 characters.
- 无法在不损害可读性、剪贴便利性或自动链接能力的前提下拆分的注释行——例如某行包含示例命令或一个超过 80 字符的原样 URL。
- a string literal that cannot easily be wrapped at 80 columns.
This may be because it contains URIs or other semantically-critical pieces,
or because the literal contains an embedded language, or because it is a
multiline literal whose newlines are significant, such as help messages.
In these cases, breaking up the literal would
reduce readability, searchability, ability to click links, etc. Except for
test code, such literals should appear at namespace scope near the top of a
file. If a tool like Clang-Format doesn't recognize the unsplittable content,
disable the tool around the content as necessary.
(We must balance between usability/searchability of such literals and the readability of the code around them.) - 无法在 80 列处轻易换行的字符串字面量。这可能是因为它包含 URI 或其他语义关键内容,或因为字面量内嵌了另一种语言,或因为它是一个多行字面量且换行本身有意义(例如帮助信息)。在这些情况下,把字面量拆开会降低可读性、可搜索性、链接可点击性等。除测试代码外,这类字面量应放在文件顶部附近的命名空间作用域。如果像 Clang-Format 这样的工具无法识别这种不可拆分内容,请按需在该内容周围禁用该工具。
(我们必须在这类字面量的可用性/可搜索性与其周围代码的可读性之间做平衡。) - an include statement.
#include语句。- a header guard.
- 头文件保护(header guard)。
- a using-declaration.
using声明。
Non-ASCII Characters 非 ASCII 字符
Non-ASCII characters should be rare, and must use UTF-8 formatting.
非 ASCII 字符应当很少出现,并且必须使用 UTF-8 编码。
You shouldn't hard-code user-facing text in source, even English, so use of non-ASCII characters should be rare. However, in certain cases it is appropriate to include such words in your code. For example, if your code parses data files from foreign sources, it may be appropriate to hard-code the non-ASCII string(s) used in those data files as delimiters. More commonly, unit test code (which does not need to be localized) might contain non-ASCII strings. In such cases, you should use UTF-8, since that is an encoding understood by most tools able to handle more than just ASCII.
你不应该在源码中硬编码面向用户的文本(即使是英文),因此非 ASCII 字符的使用也应当很少。不过,在某些情况下,在代码中包含这类词语是合适的。例如,如果你的代码要解析来自国外来源的数据文件,那么将这些数据文件中使用的非 ASCII 字符串硬编码为分隔符可能是合适的。更常见的是,单元测试代码(不需要本地化)可能包含非 ASCII 字符串。在这些情况下,你应使用 UTF-8,因为大多数能处理 ASCII 之外内容的工具都能理解这种编码。
Hex encoding is also OK, and encouraged where it
enhances readability — for example,
"\xEF\xBB\xBF", or, even more simply,
"\uFEFF", is the Unicode zero-width
no-break space character, which would be invisible
if included in the source as straight UTF-8.
十六进制转义编码也是可以的,并且在有助于提升可读性时鼓励使用——例如 "\xEF\xBB\xBF",或者更简单地写成 "\uFEFF",它表示 Unicode 的“零宽不换行空格”字符;如果直接以 UTF-8 形式包含在源码中,该字符将不可见。
When possible, avoid the u8 prefix.
It has significantly different semantics starting in C++20
than in C++17, producing arrays of char8_t
rather than char, and will change again in C++23.
You shouldn't use char16_t and
char32_t character types, since they're for
non-UTF-8 text. For similar reasons you also shouldn't
use wchar_t (unless you're writing code that
interacts with the Windows API, which uses
wchar_t extensively).
尽可能避免使用 u8 前缀。它从 C++20 开始与 C++17 的语义有显著不同:它会生成 char8_t 数组而不是 char 数组,并且在 C++23 中还会再次变化。
你不应该使用 char16_t 和 char32_t 字符类型,因为它们用于非 UTF-8 文本。出于类似原因,你也不应该使用 wchar_t(除非你在编写与 Windows API 交互的代码,因为 Windows API 会大量使用 wchar_t)。
Spaces vs. Tabs 空格与制表符
Use only spaces, and indent 2 spaces at a time.
只使用空格,并每次缩进 2 个空格。
We use spaces for indentation. Do not use tabs in your code. You should set your editor to emit spaces when you hit the tab key.
我们使用空格进行缩进。不要在代码中使用制表符(tab)。你应该将编辑器设置为在按下 tab 键时输出空格。
Function Declarations and Definitions 函数声明与定义
Return type on the same line as function name, parameters on the same line if they fit. Wrap parameter lists which do not fit on a single line as you would wrap arguments in a function call.
返回类型与函数名放在同一行;如果参数能放下,也放在同一行。若参数列表无法放在一行内,应像函数调用中的实参那样换行。
Functions look like this:
函数应写成如下形式:
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) {
DoSomething();
...
}
If you have too much text to fit on one line:
如果一行放不下太多内容:
ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
Type par_name3) {
DoSomething();
...
}
or if you cannot fit even the first parameter:
或者连第一个参数都放不下:
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 4 space indent
Type par_name2,
Type par_name3) {
DoSomething(); // 2 space indent
...
}
Some points to note:
注意事项:
- Choose good parameter names.
- 选择好的参数名。
- A parameter name may be omitted only if the parameter is not used in the function's definition.
- 只有当参数在函数定义中未被使用时,才可以省略参数名。
- If you cannot fit the return type and the function name on a single line, break between them.
- 如果返回类型和函数名无法放在同一行,在它们之间换行。
- If you break after the return type of a function declaration or definition, do not indent.
- 如果在函数声明或定义的返回类型之后换行,不要增加缩进。
- The open parenthesis is always on the same line as the function name.
- 左括号总是与函数名在同一行。
- There is never a space between the function name and the open parenthesis.
- 函数名与左括号之间绝不加空格。
- There is never a space between the parentheses and the parameters.
- 括号与参数之间绝不加空格。
- The open curly brace is always on the end of the last line of the function declaration, not the start of the next line.
- 左花括号总是位于函数声明最后一行的末尾,而不是下一行的开头。
- The close curly brace is either on the last line by itself or on the same line as the open curly brace.
- 右花括号要么单独位于最后一行,要么与左花括号位于同一行。
- There should be a space between the close parenthesis and the open curly brace.
- 右括号与左花括号之间应有一个空格。
- All parameters should be aligned if possible.
- 如果可能,所有参数应对齐。
- Default indentation is 2 spaces.
- 默认缩进为 2 个空格。
- Wrapped parameters have a 4 space indent.
- 换行后的参数使用 4 个空格缩进。
Unused parameters that are obvious from context may omit the name:
从上下文中显而易见的未使用参数可以省略参数名:
class Foo {
public:
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
};
Unused parameters that might not be obvious should comment out the variable name in the function definition:
对于不那么显而易见的未使用参数,应在函数定义中将变量名注释掉:
class Shape {
public:
virtual void Rotate(double radians) = 0;
};
class Circle : public Shape {
public:
void Rotate(double radians) override;
};
void Circle::Rotate(double /*radians*/) {}
// Bad - if someone wants to implement later, it's not clear what the
// variable means.
void Circle::Rotate(double) {}
Attributes, and macros that expand to attributes, appear at the very beginning of the function declaration or definition, before the return type:
属性(attributes)以及展开为属性的宏,应放在函数声明或定义的最前面,即返回类型之前:
ABSL_ATTRIBUTE_NOINLINE void ExpensiveFunction(); [[nodiscard]] bool IsOk();
Lambda Expressions Lambda 表达式
Format parameters and bodies as for any other function, and capture lists like other comma-separated lists.
参数与函数体的格式应与其他函数一致;捕获列表应像其他逗号分隔列表一样格式化。
For by-reference captures, do not leave a space between the
ampersand (&) and the variable name.
对于按引用捕获,不要在与号(&)和变量名之间留空格。
int x = 0;
auto x_plus_n = [&x](int n) -> int { return x + n; }
Short lambdas may be written inline as function arguments.
短 lambda 可以作为函数实参以内联形式书写。
absl::flat_hash_set<int> to_remove = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&to_remove](int i) {
return to_remove.contains(i);
}),
digits.end());
Floating-point Literals 浮点字面量
Floating-point literals should always have a radix point, with digits on both
sides, even if they use exponential notation. Readability is improved if all
floating-point literals take this familiar form, as this helps ensure that they
are not mistaken for integer literals, and that the
E/e of the exponential notation is not mistaken for a
hexadecimal digit. It is fine to initialize a floating-point variable with an
integer literal (assuming the variable type can exactly represent that integer),
but note that a number in exponential notation is never an integer literal.
浮点字面量应始终包含小数点,并且小数点两侧都要有数字,即使使用科学计数法也是如此。如果所有浮点字面量都采用这种熟悉的形式,可读性会更好:它有助于避免把它们误认为整数,也能避免把科学计数法中的 E/e 误认为十六进制数字。用整数来初始化浮点变量是可以的(前提是变量类型能精确表示该整数),但请注意,科学计数法形式的数字永远不是整数字面量。
float f = 1.f; long double ld = -.5L; double d = 1248e6;
float f = 1.0f; float f2 = 1.0; // Also OK float f3 = 1; // Also OK long double ld = -0.5L; double d = 1248.0e6;
Function Calls 函数调用
Either write the call all on a single line, wrap the arguments at the parenthesis, or start the arguments on a new line indented by four spaces and continue at that 4 space indent. In the absence of other considerations, use the minimum number of lines, including placing multiple arguments on each line where appropriate.
函数调用要么全部写在一行内,要么在括号处换行,或将参数从新的一行开始(缩进 4 个空格)并在该 4 空格缩进处继续书写。在没有其他考虑时,应使用尽可能少的行数,包括在合适时将多个参数放在同一行。
Function calls have the following format:
函数调用格式如下:
bool result = DoSomething(argument1, argument2, argument3);
If the arguments do not all fit on one line, they should be broken up onto multiple lines, with each subsequent line aligned with the first argument. Do not add spaces after the open paren or before the close paren:
如果参数无法全部放在一行内,应拆成多行,并使后续各行与第一个参数对齐。不要在左括号后或右括号前添加空格:
bool result = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
Arguments may optionally all be placed on subsequent lines with a four space indent:
参数也可以选择全部放到后续行,并使用 4 个空格缩进:
if (...) {
...
...
if (...) {
bool result = DoSomething(
argument1, argument2, // 4 space indent
argument3, argument4);
...
}
Put multiple arguments on a single line to reduce the number of lines necessary for calling a function unless there is a specific readability problem. Some find that formatting with strictly one argument on each line is more readable and simplifies editing of the arguments. However, we prioritize for the reader over the ease of editing arguments, and most readability problems are better addressed with the following techniques.
除非存在特定的可读性问题,否则应把多个参数放在同一行,以减少函数调用所需的行数。有些人认为严格做到每行一个参数更易读,也更便于编辑参数。但我们优先考虑读者体验而不是编辑便利性;而大多数可读性问题更适合用下述技巧来解决。
If having multiple arguments in a single line decreases readability due to the complexity or confusing nature of the expressions that make up some arguments, try creating variables that capture those arguments in a descriptive name:
如果把多个参数放在同一行会因为某些参数表达式过于复杂或令人困惑而降低可读性,可以尝试创建变量,用具有描述性的名称承载这些参数:
int my_heuristic = scores[x] * y + bases[x]; bool result = DoSomething(my_heuristic, x, y, z);
Or put the confusing argument on its own line with an explanatory comment:
或者把令人困惑的参数单独放在一行,并加上解释性注释:
bool result = DoSomething(scores[x] * y + bases[x], // Score heuristic.
x, y, z);
If there is still a case where one argument is significantly more readable on its own line, then put it on its own line. The decision should be specific to the argument which is made more readable rather than a general policy.
如果仍有某个参数在独立成行时可读性显著更好,那么就让它独占一行。这个决定应针对“哪个参数因此更易读”这一具体情况,而不是作为一项通用政策。
Sometimes arguments form a structure that is important for readability. In those cases, feel free to format the arguments according to that structure:
有时参数本身形成了对可读性很重要的结构。在这种情况下,可以按该结构来格式化参数:
// Transform the widget by a 3x3 matrix.
my_widget.Transform(x1, x2, x3,
y1, y2, y3,
z1, z2, z3);
Braced Initializer List Format 花括号初始化列表格式
Format a braced initializer list exactly like you would format a function call in its place.
花括号初始化列表的格式应与把它当作函数调用时的格式完全一致。
If the braced list follows a name (e.g., a type or
variable name), format as if the {} were the
parentheses of a function call with that name. If there
is no name, assume a zero-length name.
如果花括号列表跟在一个名字之后(例如类型名或变量名),就把 {} 当作该名字所对应函数调用的括号来格式化。如果没有名字,则假定名字长度为零。
// Examples of braced init list on a single line.
return {foo, bar};
functioncall({foo, bar});
std::pair<int, int> p{foo, bar};
// When you have to wrap.
SomeFunction(
{"assume a zero-length name before {"},
some_other_function_parameter);
SomeType variable{
some, other, values,
{"assume a zero-length name before {"},
SomeOtherType{
"Very long string requiring the surrounding breaks.",
some, other, values},
SomeOtherType{"Slightly shorter string",
some, other, values}};
SomeType variable{
"This is too long to fit all in one line"};
MyType m = { // Here, you could also break before {.
superlongvariablename1,
superlongvariablename2,
{short, interior, list},
{interiorwrappinglist,
interiorwrappinglist2}};
Looping and branching statements 循环与分支语句
At a high level, looping or branching statements consist of the following components:
从高层来看,循环或分支语句由以下组成部分构成:
- One or more statement keywords (e.g.,
if,else,switch,while,do, orfor). - 一个或多个语句关键字(例如
if、else、switch、while、do或for)。 - One condition or iteration specifier, inside parentheses.
- 一个位于括号内的条件或迭代说明。
- One or more controlled statements, or blocks of controlled statements.
- 一个或多个受控语句,或受控语句块。
For these statements:
对于这些语句:
- The components of the statement should be separated by single spaces (not line breaks).
- 语句的各个组成部分之间应使用单个空格分隔(而不是换行)。
- Inside the condition or iteration specifier, put one space (or a line break) between each semicolon and the next token, except if the token is a closing parenthesis or another semicolon.
- 在条件或迭代说明中,除非下一个标记是右括号或另一个分号,否则每个分号与其后的下一个标记之间应有一个空格(或换行)。
- Inside the condition or iteration specifier, do not put a space after the opening parenthesis or before the closing parenthesis.
- 在条件或迭代说明中,左括号后与右括号前都不要加空格。
- Put any controlled statements inside blocks (i.e., use curly braces).
- 将所有受控语句放入语句块中(即使用花括号)。
- Inside the controlled blocks, put one line break immediately after the opening brace, and one line break immediately before the closing brace.
- 在受控语句块内,左花括号后应立即换行,右花括号前也应立即换行。
if (condition) { // Good - no spaces inside parentheses, space before brace.
DoOneThing(); // Good - two-space indent.
DoAnotherThing();
} else if (int a = f(); a != 3) { // Good - closing brace on new line, else on same line.
DoAThirdThing(a);
} else {
DoNothing();
}
// Good - the same rules apply to loops.
while (condition) {
RepeatAThing();
}
// Good - the same rules apply to loops.
do {
RepeatAThing();
} while (condition);
// Good - the same rules apply to loops.
for (int i = 0; i < 10; ++i) {
RepeatAThing();
}
if(condition) {} // Bad - space missing after `if`.
else if ( condition ) {} // Bad - space between the parentheses and the condition.
else if (condition){} // Bad - space missing before `{`.
else if(condition){} // Bad - multiple spaces missing.
for (int a = f();a == 10) {} // Bad - space missing after the semicolon.
// Bad - `if ... else` statement does not have braces everywhere.
if (condition)
foo;
else {
bar;
}
// Bad - `if` statement too long to omit braces.
if (condition)
// Comment
DoSomething();
// Bad - `if` statement too long to omit braces.
if (condition1 &&
condition2)
DoSomething();
For historical reasons, we allow one exception to the above rules: the curly braces for the controlled statement or the line breaks inside the curly braces may be omitted if as a result the entire statement appears on either a single line (in which case there is a space between the closing parenthesis and the controlled statement) or on two lines (in which case there is a line break after the closing parenthesis and there are no braces).
出于历史原因,我们允许上述规则有一个例外:如果因此可以让整条语句要么出现在单行上(此时右括号与受控语句之间有一个空格),要么出现在两行上(此时右括号后换行且不使用花括号),那么可以省略受控语句的花括号,或省略花括号内的换行。
// OK - fits on one line.
if (x == kFoo) { return new Foo(); }
// OK - braces are optional in this case.
if (x == kFoo) return new Foo();
// OK - condition fits on one line, body fits on another.
if (x == kBar)
Bar(arg1, arg2, arg3);
This exception does not apply to multi-keyword statements like
if ... else or do ... while.
// Bad - `if ... else` statement is missing braces. if (x) DoThis(); else DoThat(); // Bad - `do ... while` statement is missing braces. do DoThis(); while (x);
Use this style only when the statement is brief, and consider that loops and branching statements with complex conditions or controlled statements may be more readable with curly braces. Some projects require curly braces always.
case blocks in switch statements can have curly
braces or not, depending on your preference. If you do include curly braces,
they should be placed as shown below.
switch (var) {
case 0: { // 2 space indent
Foo(); // 4 space indent
break;
}
default: {
Bar();
}
}
Empty loop bodies should use either an empty pair of braces or
continue with no braces, rather than a single semicolon.
while (condition) {} // Good - `{}` indicates no logic.
while (condition) {
// Comments are okay, too
}
while (condition) continue; // Good - `continue` indicates no logic.
while (condition); // Bad - looks like part of `do-while` loop.
Pointer and Reference Expressions and Types 指针与引用表达式及类型
No spaces around period or arrow. Pointer operators do not have trailing spaces.
句点(.)或箭头(->)两侧不加空格。指针运算符后也不应有空格。
The following are examples of correctly-formatted pointer and reference expressions:
下面是格式正确的指针与引用表达式示例:
x = *p; p = &x; x = r.y; x = r->y;
Note that:
请注意:
- There are no spaces around the period or arrow when accessing a member.
- 访问成员时,句点或箭头两侧不加空格。
- Pointer operators have no space after the
*or&. - 指针运算符在
*或&之后不加空格。
When referring to a pointer or reference (variable declarations or definitions, arguments, return types, template parameters, etc.), you must not place a space before the asterisk/ampersand. Use a space to separate the type from the declared name (if present).
当书写指针或引用(变量声明/定义、参数、返回类型、模板参数等)时,星号/与号之前不得加空格。应使用一个空格将类型与所声明的名称(如果有)分隔开。
// These are fine. char* c; const std::string& str; int* GetPointer(); std::vector<char*> // Note no space between '*' and '>'
It is allowed (if unusual) to declare multiple variables in the same declaration, but it is disallowed if any of those have pointer or reference decorations. Such declarations are easily misread.
在同一个声明中声明多个变量是允许的(尽管不常见),但如果其中任何一个带有指针或引用修饰,则禁止这样写。这类声明很容易被误读。
// Fine if helpful for readability. int x, y;
int x, *y; // Disallowed - no & or * in multiple declaration int *x, *y; // Disallowed - no & or * in multiple declaration int *x; // Disallowed - & or * must be left of the space char * c; // Bad - spaces on both sides of * const std::string & str; // Bad - spaces on both sides of &
Boolean Expressions 布尔表达式
When you have a boolean expression that is longer than the standard line length, be consistent in how you break up the lines.
当布尔表达式长度超过标准行宽时,应保持换行方式一致。
In this example, the logical AND operator is always at the end of the lines:
在这个例子中,逻辑与运算符总是位于行尾:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one) {
...
}
Note that when the code wraps in this example, both of
the && logical AND operators are at
the end of the line. This is more common in Google code,
though wrapping all operators at the beginning of the
line is also allowed. Feel free to insert extra
parentheses judiciously because they can be very helpful
in increasing readability when used
appropriately, but be careful about overuse. Also note that you
should always use the punctuation operators, such as
&& and ~, rather than
the word operators, such as and and
compl.
请注意:在该示例中发生换行时,两个 && 都位于行尾。这在 Google 代码中更常见,但把所有运算符放在行首换行也是允许的。可以酌情添加额外括号;恰当地使用它们有助于提高可读性,但要避免过度使用。还要注意:应始终使用标点形式的运算符(例如 &&、~),而不是单词形式的运算符(例如 and、compl)。
Return Values 返回值
Do not needlessly surround the return
expression with parentheses.
不要无谓地用括号包裹 return 表达式。
Use parentheses in return expr; only
where you would use them in x = expr;.
在 return expr; 中,仅在你也会在 x = expr; 里使用括号的情况下才使用括号。
return result; // No parentheses in the simple case.
// Parentheses OK to make a complex expression more readable.
return (some_long_condition &&
another_condition);
return (value); // You wouldn't write var = (value); return(result); // return is not a function!
Variable and Array Initialization 变量与数组初始化
You may choose between =,
(), and {}; the following are
all correct:
你可以在 =、() 与 {} 之间选择;以下写法都正确:
int x = 3;
int x(3);
int x{3};
std::string name = "Some Name";
std::string name("Some Name");
std::string name{"Some Name"};
Be careful when using a braced initialization list {...}
on a type with an std::initializer_list constructor.
A nonempty braced-init-list prefers the
std::initializer_list constructor whenever
possible. Note that empty braces {} are special, and
will call a default constructor if available. To force the
non-std::initializer_list constructor, use parentheses
instead of braces.
当对具有 std::initializer_list 构造函数的类型使用花括号初始化列表 {...} 时要小心。只要可能,非空的 braced-init-list 会优先选择 std::initializer_list 构造函数。注意空花括号 {} 是特殊情况:如果可用,会调用默认构造函数。若要强制选择非 std::initializer_list 构造函数,请用圆括号而不是花括号。
std::vector<int> v(100, 1); // A vector containing 100 items: All 1s.
std::vector<int> v{100, 1}; // A vector containing 2 items: 100 and 1.
Also, the brace form prevents narrowing of integral types. This can prevent some types of programming errors.
此外,花括号形式会阻止整数类型的窄化转换,这可以避免某些类型的编程错误。
int pi(3.14); // OK -- pi == 3.
int pi{3.14}; // Compile error: narrowing conversion.
Preprocessor Directives 预处理指令
The hash mark that starts a preprocessor directive should always be at the beginning of the line.
以 # 开头的预处理指令应始终从行首开始。
Even when preprocessor directives are within the body of indented code, the directives should start at the beginning of the line.
即使预处理指令位于缩进代码块内部,指令也应从行首开始。
// Good - directives at beginning of line
if (lopsided_score) {
#if DISASTER_PENDING // Correct -- Starts at beginning of line
DropEverything();
# if NOTIFY // OK but not required -- Spaces after #
NotifyClient();
# endif
#endif
BackToNormal();
}
// Bad - indented directives
if (lopsided_score) {
#if DISASTER_PENDING // Wrong! The "#if" should be at beginning of line
DropEverything();
#endif // Wrong! Do not indent "#endif"
BackToNormal();
}
Class Format 类格式
Sections in public, protected, and
private order, each indented one space.
按 public、protected、private 的顺序分区,每个区块关键字缩进 1 个空格。
The basic format for a class definition (lacking the comments, see Class Comments for a discussion of what comments are needed) is:
类定义的基本格式如下(不含注释;关于需要哪些注释的讨论,参见 Class Comments):
class MyClass : public OtherClass {
public: // Note the 1 space indent!
MyClass(); // Regular 2 space indent.
explicit MyClass(int var);
~MyClass() {}
void SomeFunction();
void SomeFunctionThatDoesNothing() {
}
void set_some_var(int var) { some_var_ = var; }
int some_var() const { return some_var_; }
private:
bool SomeInternalFunction();
int some_var_;
int some_other_var_;
};
Things to note:
注意事项:
- Any base class name should be on the same line as the subclass name, subject to the 80-column limit.
- 任何基类名都应与派生类名在同一行(受 80 列限制约束)。
- The
public:,protected:, andprivate:keywords should be indented one space. public:、protected:、private:关键字应缩进 1 个空格。- Except for the first instance, these keywords should be preceded by a blank line. This rule is optional in small classes.
- 除第一次出现外,这些关键字之前应有一个空行。对于小类,该规则可选。
- Do not leave a blank line after these keywords.
- 这些关键字之后不要留空行。
- The
publicsection should be first, followed by theprotectedand finally theprivatesection. public区块应放在最前,其后是protected,最后是private。- See Declaration Order for rules on ordering declarations within each of these sections.
- 关于在各区块内声明的排序规则,参见 Declaration Order。
Constructor Initializer Lists 构造函数初始化列表
Constructor initializer lists can be all on one line or with subsequent lines indented four spaces.
构造函数初始化列表可以全部写在一行内,也可以将后续行缩进 4 个空格。
The acceptable formats for initializer lists are:
初始化列表的可接受格式如下:
// When everything fits on one line:
MyClass::MyClass(int var) : some_var_(var) {
DoSomething();
}
// If the signature and initializer list are not all on one line,
// you must wrap before the colon and indent 4 spaces:
MyClass::MyClass(int var)
: some_var_(var), some_other_var_(var + 1) {
DoSomething();
}
// When the list spans multiple lines, put each member on its own line
// and align them:
MyClass::MyClass(int var)
: some_var_(var), // 4 space indent
some_other_var_(var + 1) { // lined up
DoSomething();
}
// As with any other code block, the close curly can be on the same
// line as the open curly, if it fits.
MyClass::MyClass(int var)
: some_var_(var) {}
Namespace Formatting 命名空间格式
The contents of namespaces are not indented.
命名空间内的内容不缩进。
Namespaces do not add an extra level of indentation. For example, use:
Namespaces 不会增加额外的缩进层级。例如,应写成:
namespace {
void foo() { // Correct. No extra indentation within namespace.
...
}
} // namespace
Do not indent within a namespace:
不要在命名空间内部缩进:
namespace {
// Wrong! Indented when it should not be.
void foo() {
...
}
} // namespace
Horizontal Whitespace 水平空白
Use of horizontal whitespace depends on location. Never put trailing whitespace at the end of a line.
水平空白的使用取决于所处位置。绝不要在行尾留下尾随空白。
General 一般
int i = 0; // Two spaces before end-of-line comments.
void f(bool b) { // Open braces should always have a space before them.
...
int i = 0; // Semicolons usually have no space before them.
// Spaces inside braces for braced-init-list are optional. If you use them,
// put them on both sides!
int x[] = { 0 };
int x[] = {0};
// Spaces around the colon in inheritance and initializer lists.
class Foo : public Bar {
public:
// For inline function implementations, put spaces between the braces
// and the implementation itself.
Foo(int b) : Bar(), baz_(b) {} // No spaces inside empty braces.
void Reset() { baz_ = 0; } // Spaces separating braces from implementation.
...
Adding trailing whitespace can cause extra work for others editing the same file when they merge, as can removing existing trailing whitespace. So, don't introduce trailing whitespace. Remove it if you're already changing that line, or do it in a separate clean-up operation (preferably when no one else is working on the file).
添加尾随空白会让其他编辑同一文件的人在合并时产生额外工作;移除现有的尾随空白也可能造成同样的问题。因此,不要引入尾随空白。如果你本来就要改那一行,可以顺便移除它;否则请在单独的清理操作中处理(最好在没有其他人同时编辑该文件时)。
Loops and Conditionals 循环与条件语句
if (b) { // Space after the keyword in conditions and loops.
} else { // Spaces around else.
}
while (test) {} // There is usually no space inside parentheses.
switch (i) {
for (int i = 0; i < 5; ++i) {
// Loops and conditions may have spaces inside parentheses, but this
// is rare. Be consistent.
switch ( i ) {
if ( test ) {
for ( int i = 0; i < 5; ++i ) {
// For loops always have a space after the semicolon. They may have a space
// before the semicolon, but this is rare.
for ( ; i < 5 ; ++i) {
...
// Range-based for loops always have a space before and after the colon.
for (auto x : counts) {
...
}
switch (i) {
case 1: // No space before colon in a switch case.
...
case 2: break; // Use a space after a colon if there's code after it.
Operators 运算符
// Assignment operators always have spaces around them. x = 0; // Other binary operators usually have spaces around them, but it's // OK to remove spaces around factors. Parentheses should have no // internal padding. v = w * x + y / z; v = w*x + y/z; v = w * (x + z); // No spaces separating unary operators and their arguments. x = -5; ++x; if (x && !y) ...
Templates and Casts 模板与类型转换
// No spaces inside the angle brackets (< and >), before // <, or between >( in a cast std::vector<std::string> x; y = static_cast<char*>(x); // No spaces between type and pointer. std::vector<char*> x;
Vertical Whitespace 垂直空白
Use vertical whitespace sparingly; unnecessary blank lines make it harder to see overall code structure. Use blank lines only where they aid the reader in understanding the structure.
谨慎使用垂直空白(空行);不必要的空行会使整体代码结构更难把握。只在空行有助于读者理解结构时才使用。
Do not add blank lines where indentation already provides clear delineation, such as at the start or end of a code block. Do use blank lines to separate code into closely related chunks, analogous to paragraph breaks in prose. Within a statement or declaration, usually only insert line breaks to stay within the line length limit, or to attach a comment to only part of the contents.
在缩进已经提供清晰分隔的地方不要添加空行,例如代码块的开头或结尾。应当使用空行将代码分成紧密相关的块,类似于散文中的段落分隔。在语句或声明内部,通常只为满足行宽限制或为内容的一部分附加注释而插入换行。
Exceptions to the Rules 规则的例外
The coding conventions described above are mandatory. However, like all good rules, these sometimes have exceptions, which we discuss here.
上述编码规范是强制的。然而,如同所有好的规则一样,它们有时也会有例外,我们在此讨论这些例外。
Existing Non-conformant Code 现有不符合规范的代码
You may diverge from the rules when dealing with code that does not conform to this style guide.
当处理不符合本风格指南的代码时,你可以偏离这些规则。
If you find yourself modifying code that was written to specifications other than those presented by this guide, you may have to diverge from these rules in order to stay consistent with the local conventions in that code. If you are in doubt about how to do this, ask the original author or the person currently responsible for the code. Remember that consistency includes local consistency, too.
如果你正在修改按本指南之外的规范编写的代码,为了与该代码的本地约定保持一致,你可能不得不偏离这些规则。如果你不确定该怎么做,请咨询原作者或当前负责该代码的人。请记住,一致性也包括本地一致性。
Windows Code Windows 代码
Windows programmers have developed their own set of coding conventions, mainly derived from the conventions in Windows headers and other Microsoft code. We want to make it easy for anyone to understand your code, so we have a single set of guidelines for everyone writing C++ on any platform.
Windows 程序员发展出了一套自己的编码约定,主要源自 Windows 头文件以及其他 Microsoft 代码中的约定。我们希望让任何人都能更容易理解你的代码,因此我们为任何平台上的 C++ 代码提供一套统一的指南。
It is worth reiterating a few of the guidelines that you might forget if you are used to the prevalent Windows style:
如果你习惯了常见的 Windows 风格,下面这些你可能会忘记的指南值得再强调一遍:
- Do not use Hungarian notation (for example, naming
an integer
iNum). Use the Google naming conventions, including the.ccextension for source files. - 不要使用匈牙利命名法(例如把一个整数命名为
iNum)。使用 Google 的命名约定,包括源文件使用.cc扩展名。 - Windows defines many of its own synonyms for
primitive types, such as
DWORD,HANDLE, etc. It is perfectly acceptable, and encouraged, that you use these types when calling Windows API functions. Even so, keep as close as you can to the underlying C++ types. For example, useconst TCHAR*instead ofLPCTSTR. - Windows 为基础类型定义了许多自己的同义类型,例如
DWORD、HANDLE等。在调用 Windows API 函数时使用这些类型完全可以,并且鼓励这样做。即便如此,也要尽量贴近底层的 C++ 类型。例如,使用const TCHAR*而不是LPCTSTR。 - When compiling with Microsoft Visual C++, set the compiler to warning level 3 or higher, and treat all warnings as errors.
- 使用 Microsoft Visual C++ 编译时,把编译器警告级别设为 3 或更高,并将所有警告视为错误。
- Do not use
#pragma once; instead use the standard Google include guards. The path in the include guards should be relative to the top of your project tree. - 不要使用
#pragma once;改用标准的 Google include guards。include guards 中的路径应相对你的项目根目录。 - In fact, do not use any nonstandard extensions,
like
#pragmaand__declspec, unless you absolutely must. Using__declspec(dllimport)and__declspec(dllexport)is allowed; however, you must use them through macros such asDLLIMPORTandDLLEXPORT, so that someone can easily disable the extensions if they share the code. - 事实上,除非你确实必须,否则不要使用任何非标准扩展,例如
#pragma和__declspec。允许使用__declspec(dllimport)和__declspec(dllexport);但你必须通过诸如DLLIMPORT与DLLEXPORT之类的宏来使用它们,以便他人在共享代码时可以轻松禁用这些扩展。
However, there are just a few rules that we occasionally need to break on Windows:
不过,在 Windows 上我们偶尔确实需要违反少数规则:
- Normally we strongly discourage the use of multiple implementation inheritance; however, it is required when using COM and some ATL/WTL classes. You may use multiple implementation inheritance to implement COM or ATL/WTL classes and interfaces.
- 通常我们强烈不鼓励使用多重实现继承;但在使用 COM 以及某些 ATL/WTL 类时这是必须的。你可以使用多重实现继承来实现 COM 或 ATL/WTL 的类与接口。
- Although you should not use exceptions in your own
code, they are used extensively in the ATL and some
STLs, including the one that comes with Visual C++.
When using the ATL, you should define
_ATL_NO_EXCEPTIONSto disable exceptions. You should investigate whether you can also disable exceptions in your STL, but if not, it is OK to turn on exceptions in the compiler. (Note that this is only to get the STL to compile. You should still not write exception handling code yourself.) - 尽管你不应在自己的代码中使用异常,但 ATL 以及某些 STL(包括 Visual C++ 自带的 STL)会大量使用异常。使用 ATL 时,你应定义
_ATL_NO_EXCEPTIONS来禁用异常。你也应调查你的 STL 是否可以禁用异常;如果不行,在编译器中开启异常也是可以的。(注意:这只是为了让 STL 能编译通过。你仍然不应自己编写异常处理代码。) - The usual way of working with precompiled headers
is to include a header file at the top of each source
file, typically with a name like
StdAfx.horprecompile.h. To make your code easier to share with other projects, avoid including this file explicitly (except inprecompile.cc), and use the/FIcompiler option to include the file automatically. - 使用预编译头的常见方式是在每个源文件顶部包含一个头文件,通常名为
StdAfx.h或precompile.h。为了让你的代码更易于与其他项目共享,避免显式包含该文件(precompile.cc除外),并使用编译器选项/FI来自动包含该文件。 - Resource headers, which are usually named
resource.hand contain only macros, do not need to conform to these style guidelines. - 资源头文件(通常名为
resource.h且只包含宏)不需要遵循这些风格指南。