Global objects are bad, m’kay? You will hear this all the time, from programmers young and old, recited as an article of faith. Let’s look into why this is.
A global object lives in the global namespace. There is only one of these, hence the name “global.” The global namespace is the outermost declarative region of a translation unit. A name with global namespace scope is said to be a global name. Any object with a global name is a global object.
A global object is not necessarily visible to every translation unit of a program; the one-definition rule means that it can only be defined in one translation unit. However, a declaration can be repeated in any number of translation units.
Global objects have no access restrictions. If you can see it, you can interact with it. Global objects have no owner other than the program itself, which means no sin-gle entity is responsible for it. Global objects have static storage duration, so they are initialized at startup (or static initialization) and destroyed at shutdown (or static deinitialization).
This is problematic. Ownership is fundamental to reasoning about objects. Since nothing owns a global object, how can you reason about its state at any time? You might be calling functions on that object and then, suddenly and without warning, another entity may call other functions on that object without your knowledge.
Worse still, since nothing owns global objects, their construction sequence is not determined by the standard. You have no idea in which order global objects will be constructed, which leads to a rather frustrating category of bug that we shall cover later.
Having convinced you of the harm that global objects cause to your codebase, let us turn our attention to singletons. I first encountered this term in 1994 when the book Design Patterns1 was published. This venerable tome was a tremendously exciting read at the time and is still a very useful book to have on your shelf or your e-reader. It describes patterns that recur in software engineering, in much the same way that patterns recur in conventional architecture, such as cupola, portico, or cloister. What was so welcome about this book was that it identified common patterns in program-ming and gave them names. Naming is hard, and having someone do the naming for us was a great boon.
1. Gamma, E, Helm, R, Johnson, R, and Vlissides, J, 1994. Design Patterns. Reading, MA: Addison-Wesley.
The book categorizes the patterns in three ways, as creational, structural, or behavioral patterns. It is within the creational division that we find the singleton, which restricts object creation for a class to only one instance. Of course, with such a fabulous text outlining such a well-used pattern, it was taken for granted that using a singleton was A Good Thing. After all, we had all been using something like sin-gletons for years, we just had not yet given them a name that we could all agree on.
A popular example of a singleton is the main window. The main window is where all the action happens, collecting user input and displaying results. You should only create one main window, so it might make sense to prevent the creation of another. Another example is the manager class. This is characterized by including the name “manager” in the identifier. This is a strong sign that in fact a singleton has been created, and that there are problems deciding about ownership of whatever is being managed.
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.
2. “Fiasco” is possibly an unfair characterization. Static initialization was never supposed to offer a topo-logical ordering of initialization. That was infeasible with separate compilation, incremental linking, and linkers from the 1980s. C++ had to live with the existing operating systems. This was a time when systems programmers were used to living with sharp tools.
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.
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.
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.
3. https://eel.is/c++draft/lex.name
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:
4. Meyers, S, 1998. Effective C++. Reading, MA: Addison-Wesley.
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.
You might look at that and decide that we have simply hidden a singleton behind a function. Indeed, hiding singletons is easy and the Core Guidelines remarks that enforcing their nonuse is very hard in general. The first enforcement idea offered by this specific Core Guideline I.3: “Avoid singletons,” is “look for classes with names that include singleton.” This might seem somewhat specious, but since Singleton is one of the Design Patterns it is remarkably common for engineers to add it to the name of a class, to identify that “this is a singleton” or “I have read the Design Pat-terns book.” Of course, doing so embeds the implementation in the interface, which is A Bad Thing, but that is another topic.
The second idea offered by the guideline is “look for classes for which only a single object is created (by counting objects or by examining constructors).” This requires a complete, manual, class-by-class audit of your codebase. Sometimes singletons are created by accident. An abstraction may be inducted and a class formed from it, and all the scaffolding required to manage the life cycle of and interactions with that class may be created, such as the special functions, public interface, and so on, but only one instance of the object may ever exist at one time. It may not have been the engineer’s intention to create a singleton, but that is what has happened; a count of all the instances reveals the quantity to be one.
The final idea is “If a class X has a public static function that contains a function-local static of the class type X and returns a pointer or reference to it, ban that.” This is exactly the technique described above to resolve the static initialization order fiasco. The class may have a superset of the following interface:
class Manager { public: static Manager& instance(); private: Manager(); };
The giveaway here is the private constructor. Nothing can create this object except a static member or a friend, and we see no friend declarations. Nothing can derive from it unless another constructor is added to the nonprivate interface. The private constructor indicates that “my construction is tightly controlled by other functions in my interface” and lo! And behold! The public interface contains a static function which returns a reference to an instance. You will no doubt be able to guess the gen-eral content of this member function by looking at the manager()
example function above.
A subtle variation of this is the reference-counted singleton. Consider a class that is a huge resource hog. Not only do you not want two instances of these to exist at once, but you also want it to be destroyed the moment it is no longer needed. This is somewhat complex to manage, since you need a shared pointer, a mutex, and a reference counter. However, this is still a singleton and falls under the “Avoid single-tons” guideline.
You might be looking at that public static member function and saying to yourself “surely the guideline should say ‘Avoid static storage duration objects.’ They are sin-gletons, after all.” Hold that thought.
Throughout the teaching of C++ there have been some popular examples to describe object orientation. Gas stations have cars, pumps, a cash desk, tankers delivering fuel, prices, and so on, yielding an ecosystem rich enough to describe many kinds of relationships. In the same vein, restaurants have tables, customers, menus, a serving hatch, wait staff, chefs, food deliveries, garbage collection, and other features. In today’s textbooks they probably also have a website and a Twitter account.
Both examples have one thing in common: an abstraction that should only exist singly. The gas station has one cash desk. The restaurant has one serving hatch. Surely these are singletons? If not, what is to be done?
One solution we have seen to this problem is to create a class with an entirely static interface. All the public member functions and the private data are static. We now want to take a diversion and tell you about W. Heath Robinson. Born in 1872 in Finsbury Park, London, this English cartoonist was best known for his drawings of ludicrously elaborate machines that went to great lengths to solve simple problems. One of the automatic analysis machines built for Bletchley Park during the Second World War to assist in the decryption of German message traffic was named “Heath Robinson” in his honor. I was given a book of his cartoons as a young child and marveled at the intricacy of the operation of his devices. He had an American coun-terpart, Rube Goldberg, born in July 1883 in San Francisco, who also drew overly complex devices, and inspired the board game Mouse Trap. Their names have passed into common parlance in the English language to describe overengineering.
This is precisely what a class with an entirely static interface is an example of. When you create a class, you create a public interface for viewing and controlling the abstraction, and a pile of data and nonpublic functions for modeling the abstraction. However, if there is only one instance of all the data, why do you need to attach it to a class? You can simply implement all the public member functions in one source file and put the single instance of the data and all the nonpublic functions in an anony-mous namespace.
In fact, why are you bothering with a class at all?
What we have arrived at, in a self-referentially convoluted way, is the correct solution to the problem of singletons (small s). They should be implemented as namespaces rather than classes. Rather than this:
class Manager { public: static int blimp_count(); static void add_more_blimps(int); static void destroy_blimp(int); private: static std::vector<Blimp> blimps; static void deploy_blimp(); };
you should declare this:
namespace Manager { int blimp_count(); void add_more_blimps(int); void destroy_blimp(int); }
The implementation does not need to be exposed to the client like some Heath Rob-inson drawing of marvelous and fascinating complexity. It can be hidden away in the dark recesses of a private implementation file. This has the additional advantage of improving the stability of the file in which the namespace is declared, minimizing large-scale dependent recompilation. Of course, the data used to model the abstrac-tion will not be owned by an object, so it will be static. Beware of the static initializa-tion order fiasco as described above.
You might be looking at this namespace solution and remarking to yourself “but this is still a Singleton.”
It is not a Singleton. It is a singleton. The problem that the guideline is warn-ing about is the Singleton pattern, not the existence of single-instance abstrac-tions. Indeed, in an interview with InformIT in 2009, Erich Gamma, one of the four authors of Design Patterns, remarked that he wanted to remove Singleton from the catalogue.6
6. https://www.informit.com/articles/article.aspx?p=1404056
There are two problems that we have with C++ advice. The first is that what was smart advice once may not remain smart advice forever.
At the moment, a new version of C++ is released every three years. The introduc-tion of std::unique_ptr
and std::shared_ptr
in 2011 changed the advice on how we matched new
and delete
pairs (“Don’t delete an object in a different module from where it was created”) by making it entirely feasible to never use raw new
and delete
, as advised by Core Guideline R.11: “Avoid calling new
and delete
explicitly.” Learn-ing a set of advisories and then moving on with your life is not sufficient: you need to continually review advice as the language grows and changes.
What was smart advice once may not remain smart advice forever.
An immediate manifestation of this problem is that you may have a favorite framework that you use extensively, which may contain idiomatic use of C++ that has been deprecated. Perhaps it contains a Singleton for capturing and manipulating environment variables, or settings informed by the command-line parameters which may be subject to change. You might feel that your favorite framework can do no wrong, but that is not the case. Just as scientific opinion changes with the arrival of new information, so does best C++ practice. This book that you are reading today may contain some timeless advice, but it would be supremely arrogant and foolish of me to suggest that the entire text is wisdom for the ages, with stone-carved commandments about how you should write C++.
The second problem is that advisories are the distillation of several motivations, often hidden entirely from the snappy and memorable phrase that sits in our imme-diate recall. “Avoid singletons” is much easier to remember than “avoid overengi-neering single-instance abstractions into a class and abusing access levels to prevent multiple instantiations.” Learning the advice is not enough. You must learn the moti-vations so that you know why you are taking a particular approach, and when it is safe not to do so.
C++ Core Guidelines is a living document with a GitHub repository on which you can make pull requests. It contains hundreds of advisories with varying amounts of motivation, and the purpose of this book is to highlight some of the deeper moti-vations for 30 of them.
Earlier we remarked that you may be thinking that all static objects are Single-tons, so all static objects should be avoided. You should be able to see now that static objects are not Singletons, nor are they necessarily singletons. They are an instance of an object whose duration is the entire duration of the program. Nor are they nec-essarily globals: static data members have class scope, not global scope.
Similarly, “Globals are bad, m’kay?” is not universally the case. It is global muta-ble state that can hurt you, as revealed in Core Guideline I.2: “Avoid non-const
global variables.” If your global object is immutable, then it is merely a property of your program. For example, while writing a physics simulation for a space game we could quite reasonably declare an object of type float
called G
, which is the gravitational constant, in the global namespace like this:
constexpr float G = 6.674e-11; // Gravitational constant
After all, it is a universal constant. Nobody should be changing this. Of course, you might decide that the global namespace is not the right place for such a thing, and declare a namespace called universe
like this:
namespace universe { constexpr float G = 6.674e-11; // Gravitational constant }
There is an outside chance that you might want to experiment with a universe with a different gravitational constant; in this case you may want to use a function that sim-ply returns a value, and then change the logic behind the interface according to your crazy experimental needs.
The point is that you know WHY globals are bad, for the reasons enumerated earlier, and you can decide when it is appropriate to bend that rule, with a full under-standing of the technical debt you are taking on.
In summary:
Avoid singletons: the pattern, not the single-instance abstraction.
Prefer a namespace to a class to model this type of abstraction.
Use static data carefully when implementing a singleton.
Understand the motivations for the Core Guidelines.
Review the Core Guidelines as the C++ language grows and evolves.