From 840e5839f6518a5e9f484ee95490aa4b2cad7abf Mon Sep 17 00:00:00 2001
From: Daniel Cheng Currently, code should target C++17, i.e., should not use C++2x
- features. The C++ version targeted by this guide will advance
+ features, with the exception of designated
+ initializers. The C++ version targeted by this guide will advance
(aggressively) over time.Goals of the Style Guide
C++ Version
Names and Order of Includes
.h
files.Decision on initialization
Common patterns
-
constexpr
variable of
+ string_view
, character array, or character pointer, pointing
+ to a string literal. String literals have static storage duration already
+ and are usually sufficient.
+ See TotW #140.thread_local Variables
thread_local
over other ways of defining thread-local data.
Starting with C++11, variables can be declared with the +
Variables can be declared with the
thread_local
specifier:
thread_local Foo foo = ...;@@ -1022,8 +1027,8 @@
thread_local
variables are subject to the same
initialization-order issues as static variables (and more besides).
-thread_local
variable instances are destroyed when their thread
-terminates, so they do not have the destruction-order issues of static
+
thread_local
variable instances are not destroyed before their
+thread terminates, so they do not have the destruction-order issues of static
variables.
thread_local
variable scales with
the number of running threads (in the worst case), which can be quite large
in a program.thread_local
.thread_local
.thread_local
may not be as efficient as certain compiler
intrinsics.The explicit
keyword can be applied to a constructor
-or (since C++11) a conversion operator, to ensure that it can only be
+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
-C++11's list initialization syntax:
class Foo { explicit Foo(int x, double y); ... @@ -1395,13 +1401,10 @@Copyable and Movable Types
those operations is correct. Remember to review the correctness of any defaulted operations as you would any other code. -Due to the risk of slicing, prefer to avoid providing a public assignment -operator or copy/move constructor for a class that's -intended to be derived from (and prefer to avoid deriving from a class -with such members). If your base class needs to be -copyable, provide a public virtual
+Clone()
-method, and a protected copy constructor that derived classes -can use to implement it.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.
@@ -1505,7 +1508,9 @@Inheritance
All inheritance should be
+an instance of the base class as a member instead. You may use +public
. If you want to do private inheritance, you should be including -an instance of the base class as a member instead.final
on classes when you don't intend to support using +them as base classes.Do not overuse implementation inheritance. Composition is often more appropriate. Try to restrict use of @@ -1700,10 +1705,11 @@
Declaration Order
Within each section, prefer grouping similar kinds of declarations together, and prefer the -following order: types (including
+following order: types and type aliases (typedef
, -using
,enum
, and nested structs and classes), -constants, factory functions, constructors and assignment -operators, destructor, all other methods, data members.typedef
, +using
,enum
, nested structs and classes), +static constants, factory functions, constructors and assignment +operators, destructor, all other member andfriend
functions, +data members.Do not put large method definitions inline in the class definition. Usually, only trivial or @@ -1731,7 +1737,7 @@
Inputs and Outputs
function, or both. Non-optional input parameters should usually be values orconst
references, while non-optional output and input/output parameters should usually be references (which cannot be null). -Generally, useabsl::optional
to represent optional by-value +Generally, usestd::optional
to represent optional by-value inputs, and use aconst
pointer when the non-optional form would have used a reference. Use non-const
pointers to represent optional outputs and optional input/output parameters. @@ -1890,7 +1896,7 @@Trailing Return Type Syntax
form, the return type appears before the function name. For example:int foo(int x);-The newer form, introduced in C++11, uses the
auto
+The newer form uses the
@@ -1976,7 +1982,7 @@auto
keyword before the function name and a trailing return type after the argument list. For example, the declaration above could equivalently be written:Ownership and Smart Pointers
these responsibilities are met.std::unique_ptr
is a smart pointer type -introduced in C++11, which expresses exclusive ownership +which expresses exclusive ownership of a dynamically allocated object; the object is deleted when thestd::unique_ptr
goes out of scope. It cannot be copied, but can be moved to @@ -2036,7 +2042,7 @@Ownership and Smart Pointers
where the resource releases take place.
std::unique_ptr
expresses ownership
- transfer using C++11's move semantics, which are
+ transfer using move semantics, which are
relatively new and may confuse some programmers.This prohibition also applies to the exception handling related
-features added in C++11, such as
-std::exception_ptr
and
+
This prohibition also applies to exception handling related
+features such as std::exception_ptr
and
std::nested_exception
.
There is an exception to @@ -2546,7 +2551,14 @@
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.static_cast
as the equivalent of a C-style cast
that does value conversion, when you need to
@@ -2668,10 +2680,8 @@ 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 (see e.g.,
-
-absl/strings
)
-rather than
+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.
int
. If a program needs a variable of a
different size, use a precise-width integer type from
-<stdint.h>
, such as
+<cstdint>
, such as
int16_t
. If your variable represents a
value that could ever be greater than or equal to 2^31
(2GiB), use a 64-bit type such as int64_t
.
@@ -2858,7 +2868,7 @@ C++ does not specify the sizes of integer types +
C++ does not precisely specify the sizes of integer types
like int
. Typically people assume
that short
is 16 bits,
int
is 32 bits, long
is 32 bits
@@ -3279,13 +3289,13 @@
std::unique_ptr<WidgetWithBellsAndWhistles> widget_ptr = - absl::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2); + 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_ptr = absl::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2); +auto widget_ptr = std::make_unique<WidgetWithBellsAndWhistles>(arg1, arg2); auto it = my_map_.find(key); std::array numbers = {4, 8, 15, 16, 23, 42};@@ -3450,18 +3460,18 @@Designated Initializers
While designated initializers have long been part of the C standard and supported by C++ compilers as an extension, only recently have they made it -into the draft C++ standard. They are on track for publishing in C++20.
+into the C++ standard, being added as part of C++20. -The rules in the draft 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 draft C++20 to initialize
+x
and then -z
, but noty
and thenx
.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 thenz
, but not +y
and thenx
.Use designated initializers only in the form that is compatible with the -draft C++20 standard: with initializers in the same order as the corresponding -fields appear in the struct definition.
+C++20 standard: with initializers in the same order as the corresponding fields +appear in the struct definition. @@ -3549,8 +3559,8 @@Lambda Expressions
this
by value,
+ since the use of this
is often implicit.
[&]
) only when
+the lifetime of the lambda is obviously shorter than any potential
captures.
[=]
) 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.
Do not define specializations of std::hash
.
std::hash<T>
is the function object that the
-C++11 hash containers use to hash keys of type T
,
-unless the user explicitly specifies a different hash function. For
-example, std::unordered_map<int, std::string>
is a hash
-map that uses std::hash<int>
to hash its keys,
-whereas std::unordered_map<int, std::string, MyIntHash>
-uses MyIntHash
.
std::hash
is defined for all integral, floating-point,
-pointer, and enum
types, as well as some standard library
-types such as string
and unique_ptr
. Users
-can enable it to work for their own types by defining specializations
-of it for those types.
std::hash
is easy to use, and simplifies the code
-since you don't have to name it explicitly. Specializing
-std::hash
is the standard way of specifying how to
-hash a type, so it's what outside resources will teach, and what
-new engineers will expect.
std::hash
is hard to specialize. It requires a lot
-of boilerplate code, and more importantly, it combines responsibility
-for identifying the hash inputs with responsibility for executing the
-hashing algorithm itself. The type author has to be responsible for
-the former, but the latter requires expertise that a type author
-usually doesn't have, and shouldn't need. The stakes here are high
-because low-quality hash functions can be security vulnerabilities,
-due to the emergence of
-
-hash flooding attacks.
Even for experts, std::hash
specializations are
-inordinately difficult to implement correctly for compound types,
-because the implementation cannot recursively call std::hash
-on data members. High-quality hash algorithms maintain large
-amounts of internal state, and reducing that state to the
-size_t
bytes that std::hash
-returns is usually the slowest part of the computation, so it
-should not be done more than once.
Due to exactly that issue, std::hash
does not work
-with std::pair
or std::tuple
, and the
-language does not allow us to extend it to support them.
You can use std::hash
with the types that it supports
-"out of the box", but do not specialize it to support additional types.
-If you need a hash table with a key type that std::hash
-does not support, consider using legacy hash containers (e.g.,
-hash_map
) for now; they use a different default hasher,
-which is unaffected by this prohibition.
If you want to use the standard hash containers anyway, you will -need to specify a custom hasher for the key type, e.g.,
-std::unordered_map<MyKeyType, Value, MyKeyTypeHasher> my_map; -
-Consult with the type's owners to see if there is an existing hasher -that you can use; otherwise work with them to provide one, - or roll your own.
- -We are planning to provide a hash function that can work with any type,
-using a new customization mechanism that doesn't have the drawbacks of
-std::hash
.
All global variables should have a comment describing what they -are, what they are used for, and (if unclear) why it needs to be +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. @@ -4618,33 +4563,7 @@Implementation Comments
Explanatory Comments
Tricky or complicated code blocks should have comments -before them. Example:
- -// Divide result by two, taking into account that x -// contains the carry from the add. -for (int i = 0; i < result->size(); ++i) { - x = (x << 8) + (*result)[i]; - (*result)[i] = x >> 1; - x &= 1; -} -- -Line-end Comments
- -Also, lines that are non-obvious should get a comment -at the end of the line. These end-of-line comments should -be separated from the code by 2 spaces. Example:
- -// If we have enough memory, mmap the data portion too. -mmap_budget = max<int64_t>(0, mmap_budget - index_->length()); -if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock)) - return; // Error already logged. -- -Note that there are both comments that describe what -the code is doing, and comments that mention that an -error has already been logged when the function -returns.
+before them.Function Argument Comments
@@ -4662,7 +4581,8 @@Function Argum values self-describing.
You shouldn't use the C++11 char16_t
and
+
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
@@ -5630,7 +5550,9 @@
void f(bool b) { // Open braces should always have a space before them. +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, @@ -5865,32 +5787,6 @@Windows Code
resource.h
and contain only macros, do not need to conform to these style guidelines.
Use common sense and BE CONSISTENT.
- -If you are editing code, take a few minutes to look at the
-code around you and determine its style. If they use spaces
-around their if
clauses, you should, too. If their
-comments have little boxes of stars around them, make your
-comments have little boxes of stars around them too.
The point of having style guidelines is to have a common -vocabulary of coding so people can concentrate on what you are -saying, rather than on how you are saying it. We present global -style rules here so people know the vocabulary. But local style -is also important. If code you add to a file looks drastically -different from the existing code around it, the discontinuity -throws readers out of their rhythm when they go to read it. Try -to avoid this.
- - - -OK, enough writing about writing code; the code itself is much -more interesting. Have fun!
- -