Home > Software engineering >  How can I limit my templatized singleton creation and make the code very generic?
How can I limit my templatized singleton creation and make the code very generic?

Time:01-05

I am stuck with a problem where I want to create a templatized singleton object. Something like below

template<class T>
class Singleton {
public:
 static Singleton<T>& GetInstance()
 {
   static Singleton<T> singleton;
   return singleton;
 }
 void Register(T Pointer) {/* */}
 void DoWork() { /* */ }
private:
  Singleton() {}
  std::unordered_set<T> pointers;
};

Next goal is to get access object say through a global templatized function like this

template<class T>
void Register(T Pointer) {
    Singleton<T>::GetInstance().Register(Pointer);
}

Is there a way that for a single class Hierarchy I create one singleton object. For something like below Base and derived types both result in same singleton object.

class Base{
};

class Derived : public Base {
};

CodePudding user response:

One solution is to introduce a new base class to your heiratchy that handles the registration and rely on CRTP to ensure that the instances are registered with the correct singleton. First we'll clean up the Singleton itself so that the Register takes a pointer instead of a plain type of T.

template<class T>
class Singleton {
public:

    static Singleton<T>& GetInstance()
    {
        static Singleton<T> singleton;
        return singleton;
    }

    void Register(T* /*Pointer*/) {/* */ }
};

Now we'll introduce a new templated base class with a single type argument which handles the registration with the singleton based on the type it's passed. This will be used as the base class for Base (in your sample code).

template<class T>
struct Registerable
{
    virtual ~Registerable() = default;

    virtual void Register() final
    {
        std::cout
            << "Singleton @ "
            << &Singleton<T>::GetInstance()
            << "\n";

        Singleton<T>::GetInstance().Register(static_cast<T*>(this));
    }
};

I've marked the Register function as virtual and final to prevent derived classes from overriding it. I've also added a single log to cout so that we can track which singleton is being used for registration. Now all that we need to do is have Base derive from Registerable and pass Base as the template argument (CRTP).

struct Base : Registerable<Base>
{
    // ....
};

The classes that derive from Base remain unchanged as there's nothing extra for them to do.

struct Derived : public Base {};
struct OtherDerived : public Derived {};

And now we'll run a little test code to see what happens.

int main()
{
    Base base;
    Derived derived;
    OtherDerived otherDerived;

    base.Register();
    derived.Register();
    otherDerived.Register();
}

And our output is:

Singleton @ 0015C75C
Singleton @ 0015C75C
Singleton @ 0015C75C

I don't have a lot of details on how you plan to use this so how you mold it to what you're trying to accomplish is up to you.

Note that you also have the option of making Registerable an inner class of Singleton. This allows you make the Register function in Singleton private in order to prevent it from being directly used.

template<class T>
class Singleton {
public:

    struct Registerable
    {
        virtual ~Registerable() = default;

        virtual void Register() final
        {
            std::cout
                << "Singleton @ "
                << &Singleton<T>::GetInstance()
                << "\n";

            Singleton<T>::GetInstance().Register(static_cast<T*>(this));
        }
    };

    static Singleton<T>& GetInstance()
    {
        static Singleton<T> singleton;
        return singleton;
    }

private:

    void Register(T* /*Pointer*/) {/* */ }
};

If you do this the Base class changes only slightly.

struct Base : Singleton<Base>::Registerable
{
    // ....
};

CodePudding user response:

You can simply define special type in all your bases and then use that type to determine base of hierarchy. Something like that:

#include <iostream>
#include <type_traits>

static int count = 0;

template<class T>
class SingletonImpl {
public:
 static SingletonImpl<T>& GetInstance()
 {
   static SingletonImpl<T> singleton;
   return singleton;
 }
 void Register(T Pointer) {/* */}
 void DoWork() { /* */ }
 void Print() {std::cout << "Singleton: " << num << std::endl;}
private:
  SingletonImpl() : num(count  ) {}
  int num;
};

template<typename T, typename = void>
class Singleton : public SingletonImpl<T> {};
template<typename T>
class Singleton<T, std::void_t<typename T::SingletonBase>> : public SingletonImpl<typename T::SingletonBase> {};

template<class T>
void Print() {
    Singleton<T>::GetInstance().Print();
}

class Base{
public:
    using SingletonBase = Base;
};

class Derived : public Base {
};

class Base1 {
public:
    using SingletonBase = Base1;
};

class Derived1 : public Base1 {
};

int main()
{
    Print<Base>();
    Print<Base1>();
    Print<Derived>();
    Print<Derived1>();
    Print<int>();
}

BTW, in your case your probably need to wrap T in Singleton specialization in std::remove_pointer_t<>, since in your case T is pointer and we need simple type to work on. Somethnig like that:

class Singleton<T, std::void_t<typename std::remove_pointer_t<T>::SingletonBase>> : public SingletonImpl<typename std::remove_pointer_t<T>::SingletonBase *> {};
  • Related