Rule A3-3-2 (required, implementation, automated)
Static and thread-local objects shall be constant-initialized.
Rationale
In general, using non-const global and static variables obscures the true dependencies of an API, since they can be accessed from any place of the source code. It therefore makes the code more difficult to maintain, less readable, and significantly less testable. A particular problem is that the order in which constructors and initializers for static variables are called is only partially specified by the C++ Language Standard and can even change from build to build. This can cause issues that are difficult to find or debug.
The compiler performs constant-initialization, if the object is initialized by a constexpr constructor with only constant expression as arguments; or the object is not initialized by a constructor call, but is value-initialized (T object{};); or the object is not initialized by a constructor call, but is initialized by an initializer consisting only of constant expressions. Constant initialization is guaranteed to occur before any other initialization of static or thread-local objects and may happen at compile time. Thus it is guaranteed that problematic dependencies between the initializers of constant-initialized static or thread-local objects cannot occur. Note that declaring a static variable as constexpr (static is implied in this case, but may be added to the declaration), enforces constant initialization by the compiler. Note that the rule applies to: global variables (i.e. extern) static variables static class member variables static function-scope variables
Example
// $Id: A3-3-2.cpp 305690 2018-01-29 14:35:00Z jan.babst $
#include <cstdint>
#include <limits>
#include <string>
class A
{
public:
static std::uint8_t instanceId;
static float const pi;
static std::string const separator;
A() {}
// Implementation...
};
std::uint8_t A::instanceId = 0;// Compliant - constant initialization
float const A::pi = 3.14159265359; // Compliant - constant initialization
std::string const A::separator =
"=========="; // Non-compliant - string c’tor is not constexpr
class C
{
public:
constexpr C() = default;
};
namespace
{
constexpr std::int32_t maxInt32 =
std::numeric_limits<std::int32_t>::max(); // Compliant - constexpr variable
A instance{};
// Compliant - constant (value) initialization
constexpr C c{}; // Compliant - constexpr c’tor call
} // namespace
void Fn() noexcept
{
static A a{}; // Non-compliant - A’s default c’tor is not constexpr
static std::int32_t counter{0};
// Compliant
}
static std::string border(5, ’*’);
// Non-compliant - not a constexpr c’tor
class D
{
public:
D() = default;
D(D const&) = default;
D(D&&) = default;
D& operator=(D const&) = default;
D& operator=(D&&) = default;
~D() = default;
private:
static D* instance;
};
D* D::instance = nullptr; // Compliant - initialization by constant expression
See also
cppreference.com [16]: Constant initialization. HIC++ v4.0 [9]: 3.3.1: Do not use variables with static storage duration. JSF December 2005 [8]: AV Rule 214: Assuming that non-local static objects, in separate translation units, are initialized in a special order shall not be done. SEI CERT C++ Coding Standard [10]: DCL56-CPP: Avoid cycles during initialization of static objects. C++ Core Guidelines [11]: I.22: Avoid complex initialization of global objects. Google C++ Style Guide [12]: Static and Global Variables.