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

2

3

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?

Paloma answered 9/3 at 6:37 Comment(5)
Even if C++20 modules solve this problem, in general using global variables or singletons are still bad design ideas. They both kill unit testability and create IMPLICIT dependencies between distant parts of your code base (which can be very hard to debug). So don't listen to the advice of going to singletons, lookup depencency injection instead.Heflin
You might want to watch this, the "spooky action at a distance" reference alone is worth it Value Semantics: Safety, Independence, Projection, & Future of Programming - Dave Abrahams CppCon 22Heflin
std::cout works as expected. If I needed a solution to a similar problem I would do what std::cout does. en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_CounterPahang
@PepijnKramer • I work with him. He's on my team. He's awesome!Leanneleanor
@Leanneleanor You mean Dave? Cool :)Heflin
E
2

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.

Ebro answered 9/3 at 7:8 Comment(2)
Have you reference about "initialization order of module-scope variable"?Sometimes
Note that singletons accessed through a function getter solves the static initialization order fiasco, but does not address the flipside order of destruction disaster.Leanneleanor
B
3

If A has an interface dependency on module unit B (that is, A directly or indirectly imports B), then all non-template non-inline variables defined in B are initialized before every non-template non-inline variable initialization in A.

Inline variables follow this order if all their definitions have "appearance-ordered before" relationship. (You can consider that they are initialized at one of their definitions, but it's unspecified which one is chosen.)

For variable template instantiations, the initialization is unordered, as it has always been.

Module implementation units behave as regular .cpp files in this regard.

Reference: https://eel.is/c++draft/basic.start.dynamic

Brinna answered 10/3 at 2:49 Comment(0)
E
2

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.

Ebro answered 9/3 at 7:8 Comment(2)
Have you reference about "initialization order of module-scope variable"?Sometimes
Note that singletons accessed through a function getter solves the static initialization order fiasco, but does not address the flipside order of destruction disaster.Leanneleanor

© 2022 - 2024 — McMap. All rights reserved.