5

I am working on a C++ project having multiple classes that must be singletons, with dependencies between them (order of initialization matters).

I have come up with this solution:

  1. All classes which I want to be singletons have protected constructors, e.g.:
class MySingleton1
{
protected:
    MySingleton1();
}
  1. Have a source file singleton_factory.cpp containing an instantiated class Singletons which derives from all classes which I want to be singletons, like this:
#include "MySingleton1.hpp"
#include "MySingleton2.hpp"

class Singletons : public MySingleton1, public MySingleton2 {}
static Singletons s_singletons;
  1. Still in singleton_factory.cpp, for every singleton type, also implement a specialization of a getSingleton function:
template<>
MySingleton1& getSingleton()
{
    return s_singletons;
}

template<>
MySingleton2& getSingleton()
{
    return s_singletons;
}
  1. The specializations of getSingleton will be "hid" under the generic templated variant, in singleton_factory.hpp:
template <class TSingleton>
TSingleton& getSingleton();

Advantages:

  • Low-coupling:

    • Singleton classes don't need to be "aware" of the Singletons class, the only need to hide their constructor under a protected qualifier (and that is not even mandatory, only good practice). The only code actually aware of the Singletons class is singleton_factory.cpp
    • Skinny dependencies for concrete instance: code that wants to use a singleton of type T only needs to include the header of type T and the skinny singleton_factory.hpp
  • Order of initialization can be controlled by changing the order of inheritance of the Singletons class

  • No lazy initialization => thread-safe?
  • getSingleton() is fast, no dynamic_cast, no reinterpret_cast

Disadvantages:

  • Every time a new singleton type appears, a getSingleton specialization, doing the same - i.e. "return s_singletons;" must be added to singleton_factory.cpp

So, as far as I can see, this is actually fairly good so I'm thinking to leave it like this, but I'm asking for your feedback (what better place to do that than the programming community?).

What extra advantages/disadvantages do you see with this solution?

What alternatives do you suggest?

Zuzu Corneliu
  • 1,594
  • 2
  • 15
  • 27
  • "I am working on a C++ project having multiple classes that must be singletons" Must they? What happens if they aren't? Does the world end promptly? – Christopher Pisz Dec 14 '18 at 16:56
  • 1
    @ChristopherPisz Maybe you should write an article about why the Singleton pattern is useless, maybe you'll get to be famous ;) – Zuzu Corneliu Dec 14 '18 at 16:58
  • Many people already have. Not so much useless, but it has been pointed out how much of an anti-pattern it really is. https://www.google.com/search?q=singleton+antipattern&rlz=1C1GCEU_enUS821US822&oq=singleton+antipattern&aqs=chrome..69i57j0l3.4263j1j8&sourceid=chrome&ie=UTF-8 – Christopher Pisz Dec 14 '18 at 17:19
  • May be useful: https://stackoverflow.com/a/1008289/3807729 – Galik Dec 14 '18 at 18:17
  • Object files are singleton, namespace are singleton => just use procedural programming. – Oliv Dec 14 '18 at 21:37

2 Answers2

6

This forces centralization of Singletons, which can mess up dependencies in more complex projects. The library that has singleton.cpp must depend on everything needed for every singleton. At the same time, anyone who uses a singleton must depend on the singleton.cpp library.

Basically your code could only work in a monolithic non-modular project. Scaling this to multiple dynamic libraries is next to impossible.

Your order of initialization must be maintained manually.

The static global variable's construction point is unsequenced with everything prior to the first expression in main.


A decent solution I've used is to create a dynamic library that holds the singleton memory.

To be a singleton, you inherit from a CRTP helper, which provides a ::Instance() inline method. People wanting the singleton use ::Instance().

::Instance() creates a static local variable lifetime token. It then attempts to get storage for the singleton from the primary DLL; if the object is already created, it simply casts the storage into the object type, and increases its reference count.

If not, it creates new storage and constructs the object in it.

At the destruction of the static local variable lifetime token, it reduces the reference count. If that reference count hits 0, it destroys it locally in the current dynamic library.

The lifetime of the singleton is now the union of the lifetime of the ::Instance() created variables. Destruction occurs in non-type-erased code, so we don't have to worry about the DLL with the code being unloaded. Storage is central. The DLL that stores the storage has to be lower level than every user of the Singleton system, but it in turn has no dependencies, so that isn't a pain.

This is far from perfect; singletons and lifetime are a constant problem, because clean program shutdown is hard and made harder by singleton's existence. But it has worked so far in a reasonably large project.

llllllllll
  • 16,169
  • 4
  • 31
  • 54
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Maintaining order of initialization manually is for me a +, as it gives me precise control, but I guess it can also be an annoyance in some cases. The centralization of all singletons in one translation unit is more of a + for me as well because it enables being aware of the initialization order from a single translation unit. Except for the fact that indeed this poses a problem for dynamic libraries, which is a convincing argument and got me thinking. – Zuzu Corneliu Dec 14 '18 at 16:56
  • Regarding "the static global variable's construction point is unsequenced with everything prior to the first expression in main." - that's actually the point, the Singletons class _is the enabler of this desired sequencing_ for the singleton classes. – Zuzu Corneliu Dec 14 '18 at 17:01
  • 3
    "clean program shutdown is hard and made harder by singleton's existence" From the Gospel of C++ chapter 98 verse 18. They're a constant source of problems partly because it's difficult to write reliable unit tests for static de-init issues. – AndyG Dec 14 '18 at 17:06
0

Can you use dependency injection in your case? i.e. have some serialized code create an instance of each class and pass a reference to the instance(s) into the constructors of any other instances that need access to them? This might simplify your design, if applicable. Ideally you could pass a separate, newly-created instance into each constructor to further reduce coupling but it seems you need to share state. In any case, perhaps it helps.

B. Whipple
  • 49
  • 7
  • Could you add any sample? Could you provide more detail about ideas you present? – rudolf_franek Dec 14 '18 at 18:01
  • yes, but I suggest dependency injection because of its simplicity, M. Franek. There shouldn't be a lot of moving parts. If the number of instances starts climbing then often a refactor can reduce them to a reasonable level. – B. Whipple Dec 14 '18 at 19:12