- Global objects are bad
- Singleton Design Pattern
- Static initialization order fiasco
- How to hide a singleton
- But only one of these should ever exist
- Wait a moment…
- Summary
Static initialization order fiasco
Singletons are prone to the static initialization order fiasco.2 This term was coined by Marshall Cline in his C++ FAQ and characterizes the problem of dependent objects being constructed out of order. Consider two global objects, A and B, where the con-structor of B uses some functionality provided by A and so A must be constructed first. At link time, the linker identifies the set of objects with static storage duration, sets aside an area of the memory for them to exist in, and creates a list of construc-tors to be called before main is called. At runtime, this is called static initialization.
Now, although you can identify that B depends on A and so A must be constructed first, there is no standard way to signal to the linker that this is the case. Indeed, how could you do that? You would need to find some way of exposing the dependency in the translation unit, but the compiler only knows about the translation unit it is compiling.
We can hear your brow furrowing. “Well, what if I told the linker what order to create them in? Could the linker be modified to accommodate that?” In fact, this has been tried. Long ago I used an IDE called Code Warrior, by Metrowerks. The edition I was using exposed a property that allowed me to dictate the order of construction of static objects. It worked fine, for a while, until I unwittingly created a subtle circu-lar dependency that took me the better part of twenty hours to track down.
You aren’t convinced. “Circular dependencies are part and parcel of engi-neering development. The fact that you managed to create one because you got your relationships wrong shouldn’t pre-clude the option to dictate the creation order at static initialization.” Indeed, I did actually resolve the problem and carried on, but then I needed to port the codebase to another toolchain which didn’t sup-port this feature. I was programming in nonstandard C++ and paid the price when I attempted portability.
“Nonetheless,” you continue, “this is something the committee COULD stand-ardize. Linkage specifications are already in the purview of the standard. Why not initialization order specification?” Well, another problem with static initialization order is that there is nothing to stop you starting multiple threads during static ini-tialization and requiring an object before it has been created. It is far too easy to shoot yourself in the foot with dependencies between global static objects.
The committee is not in the habit of standardizing footguns. Dependency on the order of initialization is fraught with peril, as demonstrated in the prior paragraphs, and allowing programmers to command this facility is unwise at best. Additionally, it militates against modular design. Static initialization order IS specified per transla-tion unit by order of declaration. Specification between translation units is where it all falls down. By keeping your object dependencies in a single translation unit, you avoid all of these problems while maintaining clarity of purpose and separation of concerns.
The word “linker” appears ONCE in the standard.3 Linkers are not unique to C++; linkers will bind together anything of the appropriate format, regardless of what compiler emitted it, be it C, C++, Pascal, or other languages. It is a steep demand to require that linkers suddenly support a new feature solely for the benefit of promoting a dicey programming practice in one language. Cast the idea of stand-ardizing initialization order from your mind. It is a fool’s errand.
Having said that, there is a way around the static initialization order fiasco, and that is to take the objects out of the global scope so that their initialization can be scheduled. The easiest way to do this is to create a simple function containing a static object of the type required, which the function returns by reference. This is sometimes known as the Meyers Singleton after Scott Meyers, who described this approach in his book Effective C++.4 The technique itself is much older than that, having been used in the 1980s. For example:
Manager& manager() { static Manager m; return m; }
Now the function is global, rather than the object. The Manager object will not be created until the function is called: static data at function scope falls under differ-ent initialization rules. “But,” you may ask, “what about the concurrency problem? Surely, we still have the same issue of multiple threads trying to access the object before it has been fully created?”
Fortunately, since C++11 this is also thread safe. If you look at section [stmt.dcl]5 in the standard you will see the following: “If control enters the declaration concur-rently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.” This is not the end of your troubles, though: you are still distributing a handle to a single mutable object, with no guarantee of thread-safe access to that object.