+Do not create classes only to group static member functions;
+this is no different than just giving the function names a
+common prefix, and such grouping is usually unnecessary anyway.
-
Objects with static storage duration, including global
-variables, static variables, static class member
-variables, and function static variables, must be Plain
-Old Data (POD): only ints, chars, floats, or pointers, or
-arrays/structs of POD.
-
-
The order in which class constructors and initializers
-for static variables are called is only partially
-specified in C++ and can even change from build to build,
-which can cause bugs that are difficult to find.
-Therefore in addition to banning globals of class type,
-we do not allow non-local static variables to be initialized
-with the result of a function, unless that function (such
-as getenv(), or getpid()) does not itself depend on any
-other globals. However, a static POD variable within
-function scope may be initialized with the result of a
-function, since its initialization order is well-defined
-and does not occur until control passes through its
-declaration.
+
+
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).
+
+
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.
+
-
Likewise, global and static variables are destroyed
-when the program terminates, regardless of whether the
-termination is by returning from main() or
-by calling exit(). The order in which
-destructors are called is defined to be the reverse of
-the order in which the constructors were called. Since
-constructor order is indeterminate, so is destructor
-order. For example, at program-end time a static variable
-might have been destroyed, but code still running
-— perhaps in another thread
-— tries to access it and fails. Or the
-destructor for a static string variable
-might be run prior to the destructor for another variable
-that contains a reference to that string.
-
-
One way to alleviate the destructor problem is to
-terminate the program by calling
-quick_exit() instead of exit().
-The difference is that quick_exit() does not
-invoke destructors and does not invoke any handlers that
-were registered by calling atexit(). If you
-have a handler that needs to run when a program
-terminates via quick_exit() (flushing logs,
-for example), you can register it using
-at_quick_exit(). (If you have a handler that
-needs to run at both exit() and
-quick_exit(), you need to register it in
-both places.)
-
-
As a result we only allow static variables to contain
-POD data. This rule completely disallows
-std::vector (use C arrays instead), or
-string (use const char []).
-
-
-
-
If you need a static or global
-variable of a class type, consider initializing a pointer
-(which will never be freed), from either your main()
-function or from pthread_once(). Note that this must be a
-raw pointer, not a "smart" pointer, since the smart
-pointer's destructor will have the order-of-destructor
-issue that we are trying to avoid.
+
+
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.
+
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 string kFoo = "foo";
+// bad for the same reason, even though kBar is a reference (the
+// rule also applies to lifetime-extended temporary objects)
+const 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.
+
+
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:
+
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
+where possible. Any non-local static storage
+duration variable that is not so marked should be presumed to have
+dynamic initialization, and reviewed very carefully.
+
+
By contrast, the following initializations are problematic:
+
+
time_t time(time_t*); // not constexpr!
+int f(); // not constexpr!
+struct Bar { Bar() {} };
+
+time_t m = time(nullptr); // initializing expression not a constant expression
+Foo y(f()); // ditto
+Bar b; // chosen constructor Bar::Bar() not constexpr
+
+
Dynamic initialization of nonlocal variables is discouraged, and in general
+it is forbidden. However, we do permit it if no aspect of the program depends
+on the sequencing of this initialization with respect to all other
+initializations. Under those restrictions, the ordering of the initialization
+does not make an observable difference. For example:
+
int p = getpid(); // allowed, as long as no other static variable
+ // uses p in its own initialization
+
+
Dynamic initialization of static local variables is allowed (and common).
+
+
+
+
+
Common patterns
+
+
+ - Global strings: if you require a global or static string constant,
+ consider using a simple character array, or a char pointer to the first
+ element of a string literal. String literals have static storage duration
+ already and are usually sufficient.
+ - Maps, sets, and other dynamic containers: if you require a static, fixed
+ collection, such as a set to search against or a lookup table, you cannot
+ use the dynamic containers from the standard library as a static variable,
+ since they have non-trivial destructors. Instead, consider a simple array of
+ trivial types, e.g. an array of arrays of ints (for a "map from int to
+ int"), or an array of pairs (e.g. pairs of
int and const
+ char*). For small collections, linear search is entirely sufficient
+ (and efficient, due to memory locality). If necessary, keep the collection in
+ sorted order and use a binary search algorithm. If you do really prefer a
+ dynamic container from the standard library, consider using a function-local
+ static pointer, as described below.
+ - Smart pointers (
unique_ptr, shared_ptr): smart
+ pointers execute cleanup during destruction and are therefore forbidden.
+ Consider whether your use case fits into one of the other patterns described
+ in this section. One simple solution is to use a plain pointer to a
+ dynamically allocated object and never delete it (see last item).
+ - Static variables of custom types: if you require static, constant data of
+ a type that you need to define yourself, give the type a trivial destructor
+ and a
constexpr constructor.
+ - If all else fails, you can create an object dynamically and never delete
+ it by binding the pointer to a function-local static pointer variable:
+
static const auto* const impl = new T(args...); (If the
+ initialization is more complex, it can be moved into a function or lambda
+ expression.)
+
+
+
+
Starting with C++11, variables can be declared with the
+thread_local specifier:
+
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 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 variable instances are destroyed when their thread
+terminates, so they do not have the destruction-order issues of static
+variables.
+
+
+
+
+ - Thread-local data is inherently safe from races (because only one thread
+ can ordinarily access it), which makes
thread_local useful for
+ concurrent programming.
+ thread_local is the only standard-supported way of creating
+ thread-local data.
+
+
+
+
+
+ - Accessing a
thread_local variable may trigger execution of
+ an unpredictable and uncontrollable amount of other code.
+ thread_local variables are effectively global variables,
+ and have all the drawbacks of global variables other than lack of
+ thread-safety.
+ - The memory consumed by a
thread_local variable scales with
+ the number of running threads (in the worst case), which can be quite large
+ in a program.
+ - An ordinary class member cannot be
thread_local.
+ thread_local may not be as efficient as certain compiler
+ intrinsics.
+
+
+
+
+
thread_local variables inside a function have no safety
+ concerns, so they can be used without restriction. 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:
+
+
Foo& MyThreadLocalFoo() {
+ thread_local Foo result = ComplicatedInitialization();
+ return result;
+}
+
+
+
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).
+
+
+
+
thread_local should be preferred over other mechanisms for
+ defining thread-local data.
+
+
+
Classes are the fundamental unit of code in C++. Naturally,
@@ -1086,7 +1273,7 @@
A class's public API should make explicit 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.