Wait a moment…
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
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.
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.