constinit
¶
Definition: Initialization Of Globals¶
static
localsInitialized when program flow crosses initialization the first time
Globals
Initialization in definition order per compilation unit
Initialization order of compilation units is undefined
Since 1970-01-01: integrals are initialized at compile time
Problem: The Static Initialization Order Fiasco¶
#pragma once
class point
{
public:
point(int x, int y) : _x{x}, _y{y} {}
int x() const { return _x; }
int y() const { return _y; }
private:
int _x, _y;
};
#pragma once
#include "point.h"
extern point global_point_standalone;
|
#include "global-point-standalone.h"
point global_point_standalone{42,666};
|
#pragma once
#include "point.h"
extern point global_point_depends;
|
#include "global-point-depends.h"
#include "global-point-standalone.h"
point global_point_depends{global_point_standalone};
|
#include "global-point-standalone.h"
#include "global-point-depends.h"
#include <iostream>
std::ostream& operator<<(std::ostream& o, const point& p)
{
o << '(' << p.x() << ',' << p.y() << ')';
return o;
}
int main()
{
std::cout << "global_point_standalone: " << global_point_standalone << std::endl;
std::cout << "global_point_depends: " << global_point_depends << std::endl;
return 0;
}
Compile
$ g++ -c -o global-point-depends.o global-point-depends.cpp
$ g++ -c -o global-point-standalone.o global-point-standalone.cpp
$ g++ -c -o main.o main.cpp
Link order wrong: “standalone” after “depends”
$ g++ main.o global-point-depends.o global-point-standalone.o
$ ./a.out
global_point_standalone: (42,666)
global_point_depends: (0,0)
Link order wrong: “standalone” before “depends”
$ g++ main.o global-point-standalone.o global-point-depends.o
$ ./a.out
global_point_standalone: (42,666)
global_point_depends: (42,666)
Solution: constinit
¶
The Problem¶
std::mutex
is initialized at runtimeunsigned
is initialized at compiletime (integral)⟶ this is asymmetric
Want ``std::mutex`` to be initialized at compiletime!
This should be possible because it has a
constexpr
default constructor
#include <thread>
#include <mutex>
#include <chrono>
#include <iostream>
unsigned sequence;
std::mutex sequence_lock; // <-- initialized at runtim
int main()
{
auto use_sequence = [](const char* name) {
using namespace std::chrono_literals;
while (true) {
sequence_lock.lock();
unsigned num = ++sequence;
sequence_lock.unlock();
std::cout << name << ": " << num << '\n';
std::this_thread::sleep_for(500ms);
}
};
std::thread t1(use_sequence, "thread 1");
std::thread t2(use_sequence, "thread 2");
t1.join();
t2.join();
return 0;
}
Possible Solutions?¶
Make
sequence_lock
aconstexpr
objectNo:
lock()
andunlock()
are notconst
Live with runtime initialization
No: I am programming a tiny embedded microcontroller and cannot afford any additional startup time
No: I have had enough of C++’s Static Initialization Order Fiasco
constinit
Yay
Solution: constinit
¶
#include <thread>
#include <mutex>
#include <chrono>
#include <iostream>
constinit unsigned sequence; // <-- compiletime anyway, even without constinit
constinit std::mutex sequence_lock; // <-- compiletime
int main()
{
auto use_sequence = [](const char* name) {
using namespace std::chrono_literals;
while (true) {
sequence_lock.lock();
unsigned num = ++sequence;
sequence_lock.unlock();
std::cout << name << ": " << num << '\n';
std::this_thread::sleep_for(500ms);
}
};
std::thread t1(use_sequence, "thread 1");
std::thread t2(use_sequence, "thread 2");
t1.join();
t2.join();
return 0;
}