c++singletonc++20c++-modulesstatic-order-fiasco

Does the static initialization order fiasco happens with C++20 modules?


I was told to use the singleton pattern instead of having global objects, which have the “static” storage duration, to avoid the static initialization order fiasco. But now that I have C++20 modules, should I?


Static initialization order fiasco

The static initialization order fiasco refers to the ambiguity in the order that objects with static storage duration in different translation units are initialized in. If an object in one translation unit relies on an object in another translation unit already being initialized, a crash can occur if the compiler decides to initialize them in the wrong order. For example, the order in which .cpp files are specified on the command line may alter this order. The Construct on First Use Idiom can be used to avoid the static initialization order fiasco and ensure that all objects are initialized in the correct order.

Within a single translation unit, the fiasco does not apply because the objects are initialized from top to bottom.

Storage duration

“static” storage duration.

The storage for the object is allocated when the program begins and deallocated when the program ends. Only one instance of the object exists. All objects declared at namespace scope (including global namespace) have this storage duration, plus those declared with static or extern. See “Non-local variables” and “Static local variables” for details on initialization of objects with this storage duration.

As far as I know…

The “static initialization order fiasco” means that which objects with the “static” storage duration, such as global objects, in non-module .cpp files get initialized earlier is ambiguous. It might lead to a crash where an uninitialized global object is used. Many adopted the singleton pattern that features “construct on first use (lazy construction)” to avoid this.

Then, with C++20 modules

The dependencies between C++ module files (“translation units”) are seemingly clear with the import statements in them. Does the static initialization order fiasco matters and do I still have to use the singleton pattern, instead of having import-able objects declared at the top level in C++ modules?


Solution

  • If you avoid using traditional header files, then the Static Initialization Order Fiasco does not occur with C++20 modules. C++20 modules ensure a deterministic initialization process and provide a solution to this problem by encapsulating declarations within modules and explicitly specifying dependencies between modules. This eliminates the need for header files and the potential for undefined order of initialization that can occur with traditional header-based inclusion mechanisms.

    With C++20 modules, the compiler is responsible for managing the initialization order of module-scope variables and entities (whereas a mere textual preprocessor is responsible for handling traditional headers), ensuring that dependencies are properly resolved at compile time. This mitigates the risk of encountering issues related to the static initialization order fiasco.

    Having said all of the above, the above is theory, and C++20 compiler implementations are only a few years old. So I would always test the compiler first in practice. Write a simple test case, logging each initialization to a log file (with a mutex or something similar if the program is multi-threaded) to see the actual order of initializations.

    If you find out the C++20 implementation of your compiler is bad in this regard, then using singletons is a safe solution (also for C++17 and earlier) to avoid the Static Initialization Order Fiasco.