Google C++ Style Guide

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, goto contravenes 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_ptr demonstrates 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);

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:

在头文件中定义的函数有时被称为“内联函数”。不过这是一个含义略有重叠的术语,可能指代几种不同但相互交叉的情况:

  1. A textually inline symbol's definition is exposed to the reader at the point of declaration.
  2. 文本内联(textually inline)的符号,其定义在声明处对读者可见。
  3. 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.
  4. 在头文件中定义的函数或变量是可展开内联(expandable inline)的,因为其定义可供编译器进行 内联展开,从而可能生成更高效的目标代码。
  5. ODR-safe entities do not violate the "One Definition Rule", which often requires the inline keyword for things defined in header files .
  6. 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.

尽管函数往往是更常见的困惑来源,但这些定义同样适用于变量,因此这里的规则也同样适用。

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:

只有当库要求这样做时,才应该使用尖括号路径包含头文件。特别是,以下头文件需要使用尖括号:

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:

  1. dir2/foo2.h.
  2. A blank line
  3. C system headers, and any other headers in angle brackets with the .h extension, e.g., <unistd.h>, <stdlib.h>, <Python.h>.
  4. A blank line
  5. C++ standard library headers (without file extension), e.g., <algorithm>, <cstddef>.
  6. A blank line
  7. Other libraries' .h files.
  8. A blank line
  9. Your project's .h files.

dir/foo.ccdir/foo_test.cc 中,其主要 目的是实现或测试 dir2/foo2.h 中的内容,按以下顺序包含 头文件:

  1. dir2/foo2.h
  2. 空行
  3. C 系统头文件,以及其他带 .h 扩展名且使用尖括号的头文件,例如,<unistd.h><stdlib.h><Python.h>
  4. 空行
  5. C++ 标准库头文件(无文件扩展名),例如, <algorithm><cstddef>
  6. 空行
  7. 其他库的 .h 文件。
  8. 空行
  9. 项目的 .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.ccdir/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.ccdir2/foo2.h 通常在同一个 目录中(例如,base/basictypes_test.ccbase/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::Fooproject2::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:

命名空间的使用应遵循以下原则:

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:

ifwhilefor 语句所需的变量通常应在这些语句中声明,以便这些变量被限制在这些作用域内。例如:

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.

常量初始化总是被允许的。静态存储期变量的常量初始化应标记为 constexprconstinit。任何未被如此标记的非局部静态存储期变量都应被假定为具有动态初始化,并需仔细审查。

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 常见模式

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 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.

可以在构造函数体中执行任意初始化。

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 而言,语言将其视为隐式转换。

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> 的值必须被修改)。intstd::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:

只有当这些声明/删除显而易见时,才可以省略:

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.

structclass 关键字在 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.secondstd::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。理由:标记为 overridefinal 但不是基类虚函数覆盖的函数或析构函数将无法编译,这有助于捕获常见错误。说明符充当文档;如果没有说明符,读者必须检查相关类的所有祖先,以确定函数或析构函数是否为虚函数。

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.

用户定义的字面量是创建用户定义类型对象的一种非常简洁的符号。

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:

在每个部分中,最好将相似类型的声明分组在一起,并首选以下顺序:

  1. Types and type aliases (typedef, using, enum, nested structs and classes, and friend types)
  2. 类型和类型别名(typedefusingenum、嵌套结构体和类以及 friend 类型)
  3. (Optionally, for structs only) non-static data members
  4. (可选,仅适用于结构体)非 static 数据成员
  5. Static constants
  6. 静态常量
  7. Factory functions
  8. 工厂函数
  9. Constructors and assignment operators
  10. 构造函数和赋值运算符
  11. Destructor
  12. 析构函数
  13. All other functions (static and non-static member functions, and friend functions)
  14. 所有其他函数(static 和非 static 成员函数,以及 friend 函数)
  15. All other data members (static and non-static)
  16. 所有其他数据成员(静态和非静态)

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 被销毁时,该对象将被删除。

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.

当符号 '&&' 应用于函数参数中的未限定模板参数时,适用特殊的模板参数推导规则。这种引用称为转发引用。

Do not use rvalue references (or apply the && qualifier to methods), except as follows:

不要使用右值引用(或将 && 限定符应用于方法),除非如下所示:

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++ 异常。

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_ptrstd::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。

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++ 类。这是通过使用 typeiddynamic_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 之前要三思。如果你发现自己需要根据对象的类编写行为不同的代码,请考虑以下查询类型的替代方案之一:

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++ 风格的转换。

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::cinstd::coutstd::cerrstd::clog 为控制台 I/O 提供了一流的支持。C API 也可以,但受到需要手动缓冲输入的阻碍。

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::cerrstd::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).

当变量自增 (++ii++) 或自减 (--ii--) 且表达式的值未被使用时,必须决定是使用前置自增(自减)还是后置自增(自减)。

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。这提供了关于操作可以改变哪些对象的一致的、主要是编译器验证的文档。拥有一种一致且可靠的方法来区分读和写对于编写线程安全代码至关重要,并且在许多其他上下文中也很有用。特别是:

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 一起使用而使函数定义复杂化。不要使用 constexprconsteval 强制内联。

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_tuint32_tint64_t 等类型。当你需要保证整数大小时,你应该始终优先使用这些类型,而不是 shortunsigned long long 等。最好省略这些类型的 std:: 前缀,因为额外的 5 个字符不值得增加混乱。在内置整数类型中,应仅使用 int。在适当的时候,欢迎使用标准类型别名,如 size_tptrdiff_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_tuint64_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++ 浮点类型中,唯一使用的是 floatdouble。你可以假设这些类型分别代表 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 功能。

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:

以下使用模式将避免宏的许多问题;如果你使用宏,请尽可能遵循它:

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)
auto variable declarations
auto 变量声明
A variable declaration can use the auto keyword 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>
auto can be qualified with const, 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 uses decltype(auto) instead of auto, in which case the deduced type is the result of applying decltype to 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 (and decltype(auto)) can also be used in place of a function return type. The compiler deduces the return type from the return statements in the function body, following the same rules as for variable declarations:
auto f() { return 0; }  // The return type of f is int
Lambda expression return types can be deduced in the same way, but this is triggered by omitting the return type, rather than by an explicit auto. Confusingly, trailing return type syntax for functions also uses auto in 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 int
Lambda 表达式 返回类型可以以相同的方式推导,但这是通过省略返回类型而不是通过显式 auto 触发的。令人困惑的是,函数的 尾置返回类型 语法也在返回类型位置使用 auto,但这不依赖于类型推导;它只是显式返回类型的替代语法。
Generic lambdas
泛型 Lambda
A lambda expression can use the auto keyword 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 each auto function 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 for auto variables.
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;
}
The auto can also be qualified with const, &, 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++ 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 相同的规则推导出来的。

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.

模板元编程能够提供极其灵活、类型安全且高性能的接口。没有它,诸如 GoogleTeststd::tuplestd::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 关键字提供了在模板上施加匿名约束并在编译期验证约束是否满足的机制。概念和约束经常配合使用,但也可以独立使用。

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),这是一项新的语言特性,旨在作为头文件文本包含的一种替代方案。它引入了三个新关键字来支持这一机制:moduleexportimport

模块会显著改变 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 类型),该类型决定协程的参数类型、协程如何执行,并允许在协程执行的不同阶段运行用户自定义代码。

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 特性子集。目前允许使用以下库:

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++ 标准库特性不得使用:

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

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 文件中的别名是实现细节(因为客户端代码无法引用它们),因此不受本规则限制。

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:

可接受的文件名示例:

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.hfoo_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.

类型名以大写字母开头,每个新单词的首字母也要大写,并且不使用下划线:例如 MyExcitingClassMyExcitingEnum

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_variablea_struct_data_membera_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:

被声明为 constexprconst,且其值在程序生命周期内固定不变的变量,使用前缀 “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).

嵌套命名空间应避免使用知名顶层命名空间的名字,尤其是 stdabsl,因为在 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() 的形式
uint
typedef
typedef(类型别名)
bigpos
struct or class, follows form of pos
structclass,遵循 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.ccfrobber_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:

在函数声明的注释中可以提及的内容类型:

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:

当函数参数的含义并不明显时,可以考虑以下补救措施之一:

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 个字符:

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_tchar32_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:

注意事项:

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:

从高层来看,循环或分支语句由以下组成部分构成:

For these statements:

对于这些语句:

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:

请注意:

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 代码中更常见,但把所有运算符放在行首换行也是允许的。可以酌情添加额外括号;恰当地使用它们有助于提高可读性,但要避免过度使用。还要注意:应始终使用标点形式的运算符(例如 &&~),而不是单词形式的运算符(例如 andcompl)。

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.

publicprotectedprivate 的顺序分区,每个区块关键字缩进 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:

注意事项:

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 风格,下面这些你可能会忘记的指南值得再强调一遍:

However, there are just a few rules that we occasionally need to break on Windows:

不过,在 Windows 上我们偶尔确实需要违反少数规则: