C++ assigns storage duration independently of scope. Initialization and destruction rules for static and thread storage determine whether an object is available before main, on first use, or once per thread.
Storage Duration and Initialization Phases #
Namespace-scope objects, static data members, and block-scope static objects have static storage duration. thread_local objects have thread storage duration: each thread has a distinct instance, initialized as that thread starts or first uses it according to the declaration and implementation rules.
Constant initialization occurs when a static- or thread-storage variable qualifies; otherwise the variable is zero-initialized. Together, constant initialization and zero-initialization are static initialization, which completes before dynamic initialization. Thus a constinit variable must be constant-initialized, and a constexpr object is a useful way to make an initialization requirement explicit.
constinit int process_limit = 256;
constexpr int cache_line_bytes = 64;constinit does not make an object immutable; it rejects an initializer that would require dynamic initialization.
Ordering Boundaries #
Dynamic initialization of ordinary non-inline namespace-scope variables in one translation unit is ordered by definition order. Cross-translation-unit dynamic initialization has no dependable source order: it can be indeterminately sequenced or otherwise affected by deferred initialization rules. A nonlocal initializer that reads another nonlocal object can therefore see only zero initialization rather than that object’s intended dynamic state.
Function-local statics defer construction to first control flow through the declaration:
Logger& logger() {
static Logger instance;
return instance;
}Since C++11, concurrent attempts initialize a block-scope static exactly once; other threads wait for successful initialization. If the initializer throws, initialization is retried on a later entry. First-use construction removes the cross-translation-unit construction dependency for instance, although callers must still establish any broader shutdown policy.
Destruction Is a Separate Design Problem #
Static and thread objects are destroyed at program termination or thread exit, respectively. Nonlocal destruction order across translation units is not a safe dependency mechanism; a destructor can reach an object that has already been destroyed. Function-local statics are destroyed in reverse order of completed construction, which can create a similar hazard when their destructors call other accessors.
Prefer explicit ownership and shutdown sequencing for resources that interact at teardown. Process-lifetime services may intentionally be left undestroyed when operating-system reclamation is the chosen contract; document that choice rather than relying on accidental destruction order.
Comments
Read or comment on GitHub (sign-in required to comment)