constinit And The Static Initialization Order Fiasco#

Problem: The Static Initialization Order Fiasco#

Definition: Initialization Of Globals

  • static locals

    • Initialized 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 (the epoch): integrals are initialized at compile time ⟶ this problem has been brought about by C++

SIOF Example: Mutex As A Global Variable (Demo Failure)#

  • This is a contrived example, but C++ Marketing stresses the constant initialized mutex feature a lot - so here it is

  • Compilation unit #1 contains a global std::mutex (and main() btw, which just pauses the process)

  • Compilation unit #2 contains a global std::thread object which references the mutex

  • Both the_mutex and the_thread are initialized at runtime before main in undefined order

  • This is undefined behavior - the thread could run before the mutex is initialized

  • To check, we link the program twice to reverse the order of initialization (see code/siof-mutex/CMakeLists.txt)

add_executable(c++11-constinit-mutex-runtime-init main.cpp other.cpp)
add_executable(c++11-constinit-mutex-runtime-init-reversed other.cpp main.cpp)
#include <mutex>
#include <thread>
#include <iostream>

extern std::mutex the_mutex;

std::thread the_thread([](){
    the_mutex.lock();
    std::cout << "inside critical section" << std::endl;
    the_mutex.unlock();
});

int main()
{
    the_thread.join();
    return 0;
}
#include <mutex>

std::mutex the_mutex;

SIOF Example: Global std::string Instances With Interdependencies (Demo Failure)#

  • Similar situation: std::string my_string (defined in main.cpp) depends on other_string (defined in other.cpp)

  • Demo failure: std::string constructor already appears to do magic to paper over bad language definition

  • ⟶ both programs show correct behavior - even though ill-formed

$ ./siof-string/c++11-constinit-string-runtime-init
hello world
$ ./siof-string/c++11-constinit-string-runtime-init-reversed
hello world
add_executable(c++11-constinit-string-runtime-init main.cpp other.cpp)
add_executable(c++11-constinit-string-runtime-init-reversed other.cpp main.cpp)
#include <string>
#include <iostream>

extern std::string other_string;
std::string my_string{"hello " + other_string};

int main()
{
    std::cout << my_string << std::endl;
    return 0;
}
#include <string>

std::string other_string{"world"};

SIOF Example: Global Foo Instances With Interdependencies#

  • Becoming even more contrived - although somewhat realistic

  • Foo instances depending on each other, just like std::string above

  • ⟶ Demo success

$ ./siof-foo/c++11-constinit-foo-runtime-init
42
$ ./siof-foo/c++11-constinit-foo-runtime-init-reversed
708
add_executable(c++11-constinit-foo-runtime-init main.cpp other.cpp)
add_executable(c++11-constinit-foo-runtime-init-reversed other.cpp main.cpp)
#include "foo.h"
#include <iostream>

extern Foo other_foo;
Foo my_foo{42 + other_foo.number};

int main()
{
    std::cout << my_foo.number << std::endl;
    return 0;
}
#include "foo.h"

Foo other_foo = 666;

Solution: constinit#

  • other_foo needs to be available before someone depends on it

  • constinit

  • Note that the depender cannot be constinit

  • Note: my_foo cannot be constinitextern has no idea

$ ./siof-foo-solution/c++11-constinit-foo-constinit
708
$ ./siof-foo-solution/c++11-constinit-foo-constinit-reversed
708
add_executable(c++11-constinit-foo-constinit main.cpp other.cpp)
add_executable(c++11-constinit-foo-constinit-reversed other.cpp main.cpp)
#include "foo.h"
#include <iostream>

extern Foo other_foo;
Foo my_foo{42 + other_foo.number};

int main()
{
    std::cout << my_foo.number << std::endl;
    return 0;
}
#include "foo.h"

constinit Foo other_foo = 666;