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 *> {};