Skip to Content
C++其他 C++ 特性

其他 C++ 特性

右值引用(Rvalue References)

仅在下面列出的某些特殊情况下使用右值引用。

右值引用是一种只能绑定到临时对象的引用类型。 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.

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.

  • 定义移动构造函数(接受类类型的右值引用的构造函数)使得可以移动值而不是复制它。 If v1 is a std::vector<std::string>, for example, then auto v2(std::move(v1)) will probably just result in some simple pointer manipulation instead of copying a large amount of data. In many cases this can result in a major performance improvement.
  • 右值引用使得可以实现可移动但不可拷贝的类型, which can be useful for types that have no sensible definition of copying but where you might still want to pass them as function arguments, put them in containers, etc.
  • std::move is necessary to make effective use of some standard-library types, such as std::unique_ptr.
  • Forwarding references which use the rvalue reference token make it possible to write a generic function wrapper that forwards its arguments to another function, and works whether or not its arguments are temporary objects and/or const. This is called ‘perfect forwarding’.
  • 右值引用尚未被广泛理解。 Rules like reference collapsing and the special deduction rule for forwarding references are somewhat obscure.
  • 右值引用经常被误用。 Using rvalue references is counter-intuitive in signatures where the argument is expected to have a valid specified state after the function call, or where no move operation is performed.

不要使用右值引用(或对方法应用 && 限定符),以下情况除外:

  • You may use them to define move constructors and move assignment operators (as described in Copyable and Movable Types).
  • You may use them to define &&-qualified methods that logically “consume” *this, leaving it in an unusable or empty state. Note that this applies only to method qualifiers (which come after the closing parenthesis of the function signature); if you want to “consume” an ordinary function parameter, prefer to pass it by value.
  • You may use forwarding references in conjunction with std::forward, to support perfect forwarding.
  • You may use them to define pairs of overloads, such as one taking Foo&& and the other taking const Foo&. Usually the preferred solution is just to pass by value, but an overloaded pair of functions sometimes yields better performance, for example if the functions sometimes don’t consume the input. As always: if you’re writing more complicated code for the sake of performance, make sure you have evidence that it actually helps.

友元(Friends)

我们允许在合理范围内使用 friend 类和函数。

友元通常应在同一文件中定义,这样读者不必查看另一个文件来查找类的私有成员的使用。 friend 的一个常见用途是让 FooBuilder 类成为 Foo 的友元,以便它可以正确构造 Foo 的内部状态,而不将此状态暴露给外界。 In some cases it may be useful to make a unit test class a friend of the class it tests.

友元扩展但不打破类的封装边界。 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.

异常(Exceptions)

我们不使用 C++ 异常。

  • 异常允许应用程序的更高层决定如何处理深层嵌套函数中”不可能发生”的失败, without the obscuring and error-prone bookkeeping of error codes.

    大多数其他现代语言都使用异常。 Using them in C++ would make it more consistent with Python, Java, and the C++ that others are familiar with.

  • 一些第三方 C++ 库使用异常,在内部关闭它们会使与这些库的集成更加困难。

  • 异常是构造函数失败的唯一方式。 We can simulate this with a factory function or an Init() method, but these require heap allocation or a new “invalid” state, respectively.

  • 异常在测试框架中非常方便。

  • 当你向现有函数添加 throw 语句时,你必须检查其所有传递调用者。 Either they must make at least the basic exception safety guarantee, or they must never catch the exception and be happy with the program terminating as a result. For instance, if f() calls g() calls h(), and h throws an exception that f catches, g has to be careful or it may not clean up properly.
  • 更一般地说,异常使得通过查看代码来评估程序的控制流变得困难: functions may return in places you don’t expect. This causes maintainability and debugging difficulties. You can minimize this cost via some rules on how and where exceptions can be used, but at the cost of more that a developer needs to know and understand.
  • 异常安全需要 RAII 和不同的编码实践。 Lots of supporting machinery is needed to make writing correct exception-safe code easy. Further, to avoid requiring readers to understand the entire call graph, exception-safe code must isolate logic that writes to persistent state into a “commit” phase. This will have both benefits and costs (perhaps where you’re forced to obfuscate code to isolate the commit). Allowing exceptions would force us to always pay those costs even when they’re not worth it.
  • Turning on exceptions adds data to each binary produced, increasing compile time (probably slightly) and possibly increasing address space pressure.
  • 异常的可用性可能会鼓励开发人员在不适当的时候抛出异常 or recover from them when it’s not safe to do so. For example, invalid user input should not cause exceptions to be thrown. We would need to make the style guide even longer to document these restrictions!

从表面上看,使用异常的好处大于成本,尤其是在新项目中。然而,对于现有代码,引入异常对所有依赖代码都有影响。 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 现有的代码不是异常容错的,使用异常的成本比新项目中的成本略大。 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.

我们反对使用异常的建议不是基于哲学或道德理由,而是基于实际原因。 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.

此禁令也适用于与异常处理相关的特性,如 std::exception_ptrstd::nested_exception

此规则对 Windows 代码有一个例外(无双关语意)。

noexcept

当有用且正确时,指定 noexcept

noexcept 说明符用于指定函数是否会抛出异常。 If an exception escapes from a function marked noexcept, the program crashes via std::terminate.

The noexcept operator performs a compile-time check that returns true if an expression is declared to not throw any exceptions.

  • 在某些情况下,将移动构造函数指定为 noexcept 可以提高性能, e.g., std::vector<T>::resize() moves rather than copies the objects if T’s move constructor is noexcept.
  • Specifying noexcept on a function can trigger compiler optimizations in environments where exceptions are enabled, e.g., compiler does not have to generate extra code for stack-unwinding, if it knows that no exceptions can be thrown due to a noexcept specifier.
  • In projects following this guide that have exceptions disabled it is hard to ensure that noexcept specifiers are correct, and hard to define what correctness even means.
  • It’s hard, if not impossible, to undo noexcept because it eliminates a guarantee that callers may be relying on, in ways that are hard to detect.

noexcept 准确反映函数的预期语义时,你可以出于性能原因使用它, 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 (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.

运行时类型信息(RTTI)

避免使用运行时类型信息(RTTI)。

RTTI 允许程序员在运行时查询对象的 C++ 类。这是通过使用 typeiddynamic_cast 来完成的。

RTTI 的标准替代方案(如下所述)需要修改或重新设计相关的类层次结构。 Sometimes such modifications are infeasible or undesirable, particularly in widely-used or mature code.

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 is useful when considering multiple abstract objects. Consider

bool Base::Equal(Base* other) = 0; bool Derived::Equal(Base* other) { Derived* that = dynamic_cast<Derived*>(other); if (that == nullptr) return false; ... }

在运行时查询对象的类型通常意味着设计问题。 Needing to know the type of an object at runtime is often an indication that the design of your class hierarchy is flawed.

RTTI 的无纪律使用会使代码难以维护。 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 有合法的用途,但容易被滥用,所以使用时必须小心。 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:

  • 虚方法是根据特定子类类型执行不同代码路径的首选方式。 This puts the work within the object itself.
  • If the work belongs outside the object and instead in some processing code, consider a double-dispatch solution, such as the Visitor design pattern. This allows a facility outside the object itself to determine the type of class using the built-in type system.

当程序的逻辑保证基类的给定实例实际上是特定派生类的实例时,可以自由地对该对象使用 dynamic_cast。 Usually one can use a static_cast as an alternative in such situations.

基于类型的决策树强烈表明你的代码走错了方向。

if (typeid(*data) == typeid(D1)) { ... } else if (typeid(*data) == typeid(D2)) { ... } else if (typeid(*data) == typeid(D3)) { ...

当向类层次结构添加额外的子类时,这样的代码通常会出问题。 Moreover, when properties of a subclass change, it is difficult to find and modify all the affected code segments.

不要手动实现类似 RTTI 的变通方案。 The arguments against RTTI apply just as much to workarounds like class hierarchies with type tags. Moreover, workarounds disguise your true intent.

类型转换(Casting)

使用 C++ 风格的类型转换,如 static_cast<float>(double_value),或使用花括号初始化来转换算术类型,如 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++ introduced a different cast system from C that distinguishes the types of cast operations.

C 风格转换的问题在于操作的二义性; 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++ 风格的类型转换语法冗长且笨重。

一般来说,不要使用 C 风格的类型转换。在需要显式类型转换时,使用这些 C++ 风格的类型转换。

  • Use brace initialization to convert arithmetic types (e.g., int64_t{x}). This is the safest approach because code will not compile if conversion can result in information loss. The syntax is also concise.
  • When explicitly converting to a class type, use a function-style cast; e.g., prefer std::string(some_cord) to static_cast<std::string>(some_cord).
  • Use absl::implicit_cast to safely cast up a type hierarchy, e.g., casting a Foo* to a SuperclassOfFoo* or casting a Foo* to a const Foo*. C++ usually does this automatically but some situations need an explicit up-cast, such as use of the ?: operator.
  • Use static_cast as the equivalent of a C-style cast that does value conversion, when you need to explicitly up-cast a pointer from a class to its superclass, or when you need to explicitly cast a pointer from a superclass to a subclass. In this last case, you must be sure your object is actually an instance of the subclass.
  • Use const_cast to remove the const qualifier (see const).
  • Use reinterpret_cast to do unsafe conversions of pointer types to and from integer and other pointer types, including void*. Use this only if you know what you are doing and you understand the aliasing issues. Also, consider dereferencing the pointer (without a cast) and using std::bit_cast to cast the resulting value.
  • Use std::bit_cast to interpret the raw bits of a value using a different type of the same size (a type pun), such as interpreting the bits of a double as int64_t.

See the RTTI section for guidance on the use of dynamic_cast.

流(Streams)

在适当的地方使用流,并坚持”简单”的用法。 Overload << for streaming only for types representing values, and write only the user-visible value, not any implementation details.

流是 C++ 中的标准 I/O 抽象, as exemplified by the standard header <iostream>. They are widely used in Google code, mostly for debug logging and test diagnostics.

<<>> 流运算符提供了一个易于学习、可移植、可重用和可扩展的格式化 I/O API。 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 提供一流的支持 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.

  • 流格式化可以通过修改流的状态来配置。 Such mutations are persistent, so the behavior of your code can be affected by the entire previous history of the stream, unless you go out of your way to restore it to a known state every time other code might have touched it. User code can not only modify the built-in state, it can add new state variables and behaviors through a registration system.
  • 精确控制流输出很困难, due to the above issues, the way code and data are mixed in streaming code, and the use of operator overloading (which may select a different overload than you expect).
  • 通过 << 运算符链构建输出的做法会干扰国际化, because it bakes word order into the code, and streams’ support for localization is flawed .
  • 流 API 是微妙且复杂的, so programmers must develop experience with it in order to use it effectively.
  • 解析 << 的众多重载对编译器来说代价极高。 When used pervasively in a large codebase, it can consume as much as 20% of the parsing and semantic analysis time.

仅在流是完成工作的最佳工具时使用它们。 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 或处理不受信任的数据。 Instead, find and use the appropriate templating libraries to handle issues like internationalization, localization, and security hardening.

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.

仅当你的类型表示一个值时,才为你的类型重载 << 作为流运算符, 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).

前置自增和前置自减

使用自增和自减运算符的前置形式(++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).

后缀自增/自减表达式求值为修改之前的值。 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.

使用前缀自增/自减,除非代码明确需要后缀自增/自减表达式的结果。

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; };).

使人们更容易理解变量是如何被使用的。 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. 帮助人们知道在多线程程序中哪些函数可以安全地在不加锁的情况下使用。

const 是具有传播性的:如果你将 const 变量传递给函数,该函数的原型中必须有 const (or the variable will need a const_cast). This can be a particular problem when calling library functions.

我们强烈建议在 API 中使用 const (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:

  • If a function guarantees that it will not modify an argument passed by reference or by pointer, the corresponding function parameter should be a reference-to-const (const T&) or pointer-to-const (const T*), respectively.
  • For a function parameter passed by value, const has no effect on the caller, thus is not recommended in function declarations. See TotW #109 .
  • Declare methods to be const unless they alter the logical state of the object (or enable the user to modify that state, e.g., by returning a non-const reference, but that’s rare), or they can’t safely be invoked concurrently.

在局部变量上使用 const 既不鼓励也不反对。

类的所有 const 操作应该可以安全地相互并发调用。 If that’s not feasible, the class must be clearly documented as “thread-unsafe”.

const 放在哪里

有些人喜欢 int const* foo 的形式而不是 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).

也就是说,虽然我们鼓励将 const 放在前面,但我们不做强制要求。但要与你周围的代码保持一致!

constexpr、constinit 和 consteval 的使用

使用 constexpr 来定义真常量或确保常量初始化。 Use constinit to ensure constant initialization for non-constant variables.

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 可以用浮点表达式而不仅仅是字面量来定义常量; definition of constants of user-defined types; and definition of constants with function calls.

过早地将某些东西标记为 constexpr 可能会在以后需要降级时导致迁移问题。 Current restrictions on what is allowed in constexpr functions and constructors may invite obscure workarounds in these definitions.

constexpr 定义能够对接口的常量部分进行更健壮的规范。 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.

整数类型

在内置 C++ 整数类型中,唯一使用的是 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++ 没有为整数类型指定精确的大小 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++ 中整数类型的大小可能因编译器和架构而异。

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.

我们经常使用 int,用于我们知道不会太大的整数,例如循环计数器。 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.

你不应该使用无符号整数类型(如 uint32_t),除非有正当理由,如表示位模式而不是数字,或者你需要定义的 2^N 模溢出。 In particular, do not use unsigned types to say a number will never be negative. Instead, use assertions for this.

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.

转换整数类型时要小心。整数转换和提升可能导致未定义行为,引发安全漏洞和其他问题。

关于无符号整数

无符号整数适合表示位域和模运算。 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.

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.

浮点类型

在内置 C++ 浮点类型中,唯一使用的是 floatdouble。 You may assume that these types represent IEEE-754 binary32 and binary64, respectively.

不要使用 long double,因为它会给出不可移植的结果。

架构可移植性

编写架构可移植的代码。不要依赖特定于单个处理器的 CPU 特性。

  • When printing values, use type-safe numeric formatting libraries like absl::StrCat, absl::Substitute, absl::StrFormat, or std::ostream instead of the printf family of functions.

  • When moving structured data into or out of your process, encode it using a serialization library like Protocol Buffers  rather than copying the in-memory representation around.

  • If you need to work with memory addresses as integers, store them in uintptr_ts rather than uint32_ts or uint64_ts.

  • Use braced-initialization as needed to create 64-bit constants. For example:

    int64_t my_value{0x123456789}; uint64_t my_mask{uint64_t{3} << 48};
  • Use portable floating point types; avoid long double.

  • Use portable integer types; avoid short, long, and long long.

预处理器宏(Preprocessor Macros)

避免定义宏,尤其是在头文件中;优先使用内联函数、枚举和 const 变量。使用项目特定的前缀来命名宏。不要使用宏来定义 C++ API 的部分。

宏意味着你看到的代码不是编译器看到的代码。 This can introduce unexpected behavior, especially since macros have global scope.

当宏被用来定义 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:

class WOMBAT_TYPE(Foo) { // ... public: EXPAND_PUBLIC_WOMBAT_API(Foo) EXPAND_WOMBAT_COMPARISONS(Foo, ==, <) };

幸运的是,宏在 C++ 中远不如在 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.

Macros can do things these other techniques cannot, and you do see them in the codebase, especially in the lower-level libraries. And some of their special features (like stringifying, concatenation, and so forth) are not available through the language proper. But before using a macro, consider carefully whether there’s a non-macro way to achieve the same result. If you need to use a macro to define an interface, contact your project leads to request a waiver of this rule.

The following usage pattern will avoid many problems with macros; if you use macros, follow it whenever possible:

  • Don’t define macros in a .h file.
  • #define macros right before you use them, and #undef them right after.
  • Do not just #undef an existing macro before replacing it with your own; instead, pick a name that’s likely to be unique.
  • Try not to use macros that expand to unbalanced C++ constructs, or at least document that behavior well.
  • Prefer not using ## to generate function/class/variable names.

从头文件导出宏(即在头文件中定义它们而不在头文件结束前 #undef 它们)是极其强烈不鼓励的。 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).

0 和 nullptr/NULL

对指针使用 nullptr,对字符使用 '\0'(而不是 0 字面量)。

对于指针(地址值),使用 nullptr,因为这提供了类型安全。

对空字符使用 '\0'。使用正确的类型使代码更具可读性。

sizeof

优先使用 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.

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)(包括 auto)

仅当类型推导使不熟悉项目的读者更容易理解代码,或使代码更安全时才使用。不要仅仅为了避免编写显式类型的不便而使用它。

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:

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)

auto variable declarations
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.

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.

Generic lambdas 
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 init captures 
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.

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

(These summaries omit many details and caveats; see the links for further information.)

  • C++ 类型名可能很长且笨重,尤其是涉及模板或命名空间时。
  • When a C++ type name is repeated within a single declaration or a small code region, the repetition may not be aiding readability.
  • It is sometimes safer to let the type be deduced, since that avoids the possibility of unintended copies or type conversions.

C++ 代码在类型明确时通常更清晰, especially when type deduction would depend on information from distant parts of the code. In expressions like:

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.

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.

基本规则是:仅为了使代码更清晰或更安全而使用类型推导,不要仅为了避免编写显式类型的不便而使用它。 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.

These principles apply to all forms of type deduction, but the details vary, as described in the following sections.

函数模板参数推导

函数模板参数推导几乎总是可以的。 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.

局部变量类型推导

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:

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.

返回类型推导

仅当函数体只有很少的 return 语句时,才使用返回类型推导(适用于函数和 Lambda), 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.

参数类型推导

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 初始化捕获

Init captures are covered by a more specific style rule, which largely supersedes the general rules for type deduction.

结构化绑定

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.

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.

类模板参数推导(CTAD)

仅对已明确选择支持类模板参数推导的模板使用它。

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

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:

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 有时可以允许你从代码中省略样板代码。

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.

CTAD 也有许多与 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,除非该模板的维护者已明确选择支持使用 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.

Uses of CTAD must also follow the general rules on Type deduction.

指定初始化器(Designated Initializers)

仅以符合 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}.

指定初始化器可以使聚合表达式变得方便且高度可读, especially for structs with less straightforward ordering of fields than the Point example above.

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.

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++20 标准的形式使用指定初始化器: with initializers in the same order as the corresponding fields appear in the struct definition.

Lambda 表达式

在适当的地方使用 Lambda 表达式。当 Lambda 将逃逸出当前作用域时,优先使用显式捕获。

Lambda 表达式是创建匿名函数对象的简洁方式。 They’re often useful when passing functions as arguments. For example:

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:

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:

[foo = std::vector<int>({1, 2, 3})] () { ... }

The type of a capture with an initializer is deduced using the same rules as auto.

  • Lambda 比定义函数对象的其他方式简洁得多 to be passed to STL algorithms, which can be a readability improvement.
  • Appropriate use of default captures can remove redundancy and highlight important exceptions from the default.
  • Lambdas, std::function, and std::bind can be used in combination as a general purpose callback mechanism; they make it easy to write functions that take bound functions as arguments.
  • Lambda 中的变量捕获可能是悬空指针错误的来源, particularly if a lambda escapes the current scope.
  • Default captures by value can be misleading because they do not prevent dangling-pointer bugs. Capturing a pointer by value doesn’t cause a deep copy, so it often has the same lifetime issues as capture by reference. This is especially confusing when capturing this by value, since the use of this is often implicit.
  • Captures actually declare new variables (whether or not the captures have initializers), but they look nothing like any other variable declaration syntax in C++. In particular, there’s no place for the variable’s type, or even an auto placeholder (although init captures can indicate it indirectly, e.g., with a cast). This can make it difficult to even recognize them as declarations.
  • Init captures inherently rely on type deduction, and suffer from many of the same drawbacks as auto, with the additional problem that the syntax doesn’t even cue the reader that deduction is taking place.
  • It’s possible for use of lambdas to get out of hand; very long nested anonymous functions can make code harder to understand.
  • Use lambda expressions where appropriate, with formatting as described below.

  • Prefer explicit captures if the lambda may escape the current scope. For example, instead of:

    { Foo foo; ... executor->Schedule([&] { Frobnicate(foo); }) ... } // BAD! The fact that the lambda makes use of a reference to `foo` and // possibly `this` (if `Frobnicate` is a member function) may not be // apparent on a cursory inspection. If the lambda is invoked after // the function returns, that would be bad, because both `foo` // and the enclosing object could have been destroyed.

    prefer to write:

    { Foo foo; ... executor->Schedule([&foo] { Frobnicate(foo); }) ... } // BETTER - The compile will fail if `Frobnicate` is a member // function, and it's clearer that `foo` is dangerously captured by // reference.
  • Use default capture by reference ([&]) only when the lifetime of the lambda is obviously shorter than any potential captures.

  • Use default capture by value ([=]) only as a means of binding a few variables for a short lambda, where the set of captured variables is obvious at a glance, and which does not result in capturing this implicitly. (That means that a lambda that appears in a non-static class member function and refers to non-static class members in its body must capture this explicitly or via [&].) Prefer not to write long or complex lambdas with default capture by value.

  • Use captures only to actually capture variables from the enclosing scope. Do not use captures with initializers to introduce new names, or to substantially change the meaning of an existing name. Instead, declare a new variable in the conventional way and then capture it, or avoid the lambda shorthand and define a function object explicitly.

  • See the section on type deduction for guidance on specifying the parameter and return types.

模板元编程(Template Metaprogramming)

避免复杂的模板编程。

模板元编程是指一系列利用 C++ 模板实例化机制是图灵完备的并且可以用于在类型域中执行任意编译时计算这一事实的技术。

模板元编程允许极其灵活、类型安全且高性能的接口。 Facilities like GoogleTest , std::tuple, std::function, and Boost.Spirit would be impossible without it.

模板元编程中使用的技术对于语言专家以外的人通常是晦涩的。 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.

模板元编程有时允许比没有它时更干净、更易用的接口, 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 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.

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)

谨慎使用概念(Concepts)。 一般来说,概念和约束只应在 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.

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.

  • 概念允许编译器在涉及模板时生成更好的错误消息, which can reduce confusion and significantly improve the development experience.
  • Concepts can reduce the boilerplate necessary for defining and using compile-time constraints, often increasing the clarity of the resulting code.
  • Constraints provide some capabilities that are difficult to achieve with templates and SFINAE techniques.
  • 与模板一样,概念可以使代码显著更复杂且更难以理解。
  • Concept syntax can be confusing to readers, as concepts appear similar to class types at their usage sites.
  • Concepts, especially at API boundaries, increase code coupling, rigidity, and ossification.
  • Concepts and constraints can replicate logic from a function body, resulting in code duplication and increased maintenance costs.
  • Concepts muddy the source of truth for their underlying contracts, as they are standalone named entities that can be utilized in multiple locations, all of which evolve separately from each other. This can cause the stated and implied requirements to diverge over time.
  • Concepts and constraints affect overload resolution in novel and non-obvious ways.
  • As with SFINAE, constraints make it harder to refactor code at scale.

当存在等价的预定义概念时,应优先使用标准库中的预定义概念而不是类型特征。 (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.

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.

不要定义与函数体重复的概念, 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.

概念应该能被编译器静态验证。 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 模块(Modules)。

C++20 引入了”模块(Modules)“,一种设计为头文件文本包含的替代方案的新语言特性。 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.

协程(Coroutines)

仅通过已获项目负责人批准的库使用 C++20 协程。

C++20 引入了协程(Coroutines) :可以暂停并稍后恢复执行的函数。 They are especially convenient for asynchronous programming, where they can provide substantial improvements over traditional callback-based frameworks.

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.

  • 协程可用于实现适合特定任务的安全高效库,例如异步编程。
  • 协程在语法上与非协程函数几乎相同, which can make them substantially more readable than alternatives.
  • The high degree of customization makes it possible to insert more detailed debugging information into coroutines, compared to alternatives.
  • There is no standard coroutine promise type, and each user-defined implementation is likely going to be unique in some aspect.
  • Because of load-bearing interactions between the return type, the various customizable hooks in the promise type, and compiler-generated code, coroutine semantics are extremely difficult to deduce from reading user code.
  • The many customizable aspects of coroutines introduce a large number of pitfalls, especially around dangling references and race conditions.

In summary, designing a high-quality and interoperable coroutine library requires a large amount of difficult work, careful thought, and extensive documentation.

仅使用已获项目负责人批准在项目范围内使用的协程库。 不要自行编写 Promise 或 Awaitable 类型。

Boost

仅使用 Boost 库集合中经批准的库。

The Boost library collection  is a popular collection of peer-reviewed, free, open-source C++ libraries.

Boost 代码通常质量很高,可广泛移植,并填补了 C++ 标准库中的许多重要空白, such as type traits and better binders.

一些 Boost 库鼓励可能会妨碍可读性的编码实践, such as metaprogramming and other advanced template techniques, and an excessively “functional” style of programming.

为了保持所有可能阅读和维护代码的贡献者的高可读性水平,我们只允许使用经批准的 Boost 特性子集。目前,允许使用以下库:

我们正在积极考虑将其他 Boost 特性添加到列表中,因此此列表将来可能会扩展。

禁用的标准库特性

Boost 一样,一些现代 C++ 库功能鼓励妨碍可读性的编码实践 — 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.

以下 C++ 标准库特性不得使用:

  • 编译时有理数(<ratio>),因为担心它与更重模板的接口风格相关。
  • <cfenv><fenv.h> 头文件,因为许多编译器不能可靠地支持这些功能。
  • <filesystem> 头文件,它没有足够的测试支持,且存在固有的安全漏洞。

非标准扩展(Nonstandard Extensions)

除非另有说明,否则不得使用 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++ 中不存在的有用功能。
  • Important performance guidance to the compiler can only be specified using extensions.
  • 非标准扩展并非在所有编译器中都有效。使用非标准扩展会降低代码的可移植性。
  • Even if they are supported in all targeted compilers, the extensions are often not well-specified, and there may be subtle behavior differences between compilers.
  • Nonstandard extensions add features to the language that a reader must know to understand the code.
  • Nonstandard extensions require additional work to port across architectures.

不要使用非标准扩展。你可以使用可移植性包装器 that are implemented using nonstandard extensions, so long as those wrappers are provided by a designated project-wide portability header.

别名(Aliases)

公共别名是为了 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.

在新代码中,using 优于 typedef, because it provides a more consistent syntax with the rest of C++ and works with templates.

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.

  • 别名可以通过简化长或复杂的名称来提高可读性。
  • Aliases can reduce duplication by naming in one place a type used repeatedly in an API, which might make it easier to change the type later.
  • When placed in a header where client code can refer to them, aliases increase the number of entities in that header’s API, increasing its complexity.
  • Clients can easily rely on unintended details of public aliases, making changes difficult.
  • It can be tempting to create a public alias that is only intended for use in the implementation, without considering its impact on the API, or on maintainability.
  • 别名可能会带来名称冲突的风险。
  • Aliases can reduce readability by giving a familiar construct an unfamiliar name.
  • Type aliases can create an unclear API contract: it is unclear whether the alias is guaranteed to be identical to the type it aliases, to have the same API, or only to be usable in specified narrow ways.

不要仅为了在实现中节省打字而在公共 API 中放置别名;只有当你打算让客户端使用它时才这样做。

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

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:

// In a .cc file using ::foo::Bar;

Switch 语句

如果不是基于枚举值的条件,switch 语句应始终有一个 default 分支 (in the case of an enumerated value, the compiler will warn you if any values are not handled). 如果默认分支永远不应执行,请将其视为错误。 For example:

switch (var) { case 0: { ... break; } case 1: { ... break; } default: { LOG(FATAL) << "Invalid value in switch statement: " << var; } }

从一个 case 标签贯穿到另一个必须使用 [[fallthrough]]; 属性标注。 [[fallthrough]]; should be placed at a point of execution where a fall-through to the next case label occurs. 一个常见的例外是没有中间代码的连续 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; }
Last updated on