What is `constinit` in C++20?
- What does
constinit
mean? Why was it introduced? In which cases should we use it?
Initializing a variable with static storage duration might result in two outcomes¹:
The variable is initialized at compile-time (constant-initialization);
The variable is initialized the first time control passes through its declaration.
Case (2) is problematic because it can lead to the static initialization order fiasco, which is a source of dangerous bugs related to global objects.
The constinit
keyword can only be applied on variables with static storage duration. If the decorated variable is not initialized at compile-time, the program is ill-formed (i.e. does not compile).
Using constinit
ensures that the variable is initialized at compile-time, and that the static initialization order fiasco cannot take place.
- Does it make a variable immutable? Does it imply
const
orconstexpr
?
No and no.
However, constexpr
does imply constinit
.
- Can a variable be both
const
andconstinit
? What aboutconstexpr
andconstinit
?
It can be both const
and constinit
. It cannot be both constexpr
and constinit
. From the wording:
At most one of the
constexpr
,consteval
, andconstinit
keywords shall appear in a decl-specifier-seq.
constexpr
is not equivalent to const constinit
, as the former mandates constant destruction, while the latter doesn't.
- To which variables can the specifier be applied? Why cannot we apply it to non-
static
, non-thread_local
variables?
It can only be applied to variables with static or thread storage duration. It does not make sense to apply it to other variables, as constinit
is all about static initialization.
- Does it have any performance advantages?
No. However, a collateral benefit of initializing a variable at compile-time is that it doesn't take instructions to initialize during program execution. constinit
helps developers ensure that is the case without having to guess or check the generated assembly.
¹: See https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
MAIN PROBLEM:
An object is considered to be initialized only when the control passes through its declaration or its definition; otherwise (i.e the control jump into a function defined in the source file in which this object is declared or defined, it doesn't see it at all) any access to this uninitialized object is undefined behavior.
Moreover, the order of initializing static-duration objects defined into multiple translation units is also undefined. You don't have a way in code to request the compiler to initialize a static object before or after another one because one object is depending on the other. Actually you cannot do this. It's up to the compiler to decide which object should be initialized first; in particular this actually depends on the order of compiling each source file.
Example - Segmentation fault
// main.cpp
#include "src1.h"
A a{ 10 }; // declaring an object of class A with static duration.
int main() {}
// src1.cpp
#include "src1.h"
#include "src2.h"
B b{ 20 }; // declaring an object of class B with static duration.
A::A(int x): m_x(x) { b.f(); }
//src2.cpp
#include "src2.h"
int B::f() { return m_x; }
B::B(int x): m_x(x) { }
//src1.h
struct A {
private: int m_x;
public: A(int);
};
//src2.h
struct B {
private: int m_x;
public: B(int); int f();
};
g++ main.cpp src1.cpp src2.cpp // OK: main.cpp should be compiled first
g++ main.cpp src2.cpp src1.cpp // OK: main.cpp should be compiled first
g++ any_other_order // sigfault
WORKAROULD:
constinit gets introduced in C++20