Introduction
This article will show how you can create a singleton which uses parameters in its constructor. It's rare when it needs to add parameters to the singleton on construct, but sometimes it is necessary. Imagine that you have a class named Platform
which contains factories and it creates platform dependent objects (mutexes, threads and file system objects). We don't want to write this Platform
class in every operating system and we want to use only one Platform
singleton to create platform dependent objects. We are using interfaces (IThread
, IMutex
) and implementing it on all platforms. On the initialization of the Platform
, we give these platform dependent objects and the Platform
will clone them when we need a new mutex or thread.
This article won't introduce you to a full featured singleton. My goal is to make a simple singleton which requires parameters in its constructor. If you want to learn more about singletons, I suggest you read Andrei Alexandrescu's book Modern C++ Design: Generic Programming and Design Patterns Applied.
Conception and the Goal
I need a clear, simple and easy to use solution, like this:
SingletonFactory<Platform>().create(new Thread_linux, new Mutex_linux);
IThread* pThread = Platform::instance()->threadFactory()->create();
The expectations are the follows:
- The instantiation of the singleton and the query needs to be separate. Don't use one function for two different operations.
- Only on the creation and only once, you need to pass the parameters.
- The number of parameters have to be variable.
- Clear and simple solution.
- Have an option to instantiate the singleton at compile time and also check it at compile time.
The Singleton
First, let's make a template singleton class, which will create any type of singleton for us.
template <typename _Ty>
class Singleton
{
friend class SingletonFactory<_Ty>;
protected:
Singleton()
{
}
Singleton(const Singleton& other)
{
}
Singleton & operator=(const Singleton& other)
{
return *this;
}
public:
static _Ty* instance()
{
return mp_Instance.get();
}
protected:
static std::auto_ptr<_Ty> mp_Instance;
};
#ifndef SINGLETON_COMPILE_TIME_CHECK
template <typename _Ty> std::auto_ptr<_Ty> Singleton<_Ty>::mp_Instance;
#endif
I use auto_ptr
to release the instance to keep the code simple. I enclose the initialization of the static mp_Instance
with an ifdef
macro, because if you define the SINGLETON_COMPILE_CHECK
, you have to initialize the static
object. It constrains you to initialize it and do it only once.
Now we can write a factory which will create the singletons.
The Singleton Factory
template <typename _TSingleton>
class SingletonFactory
{
public:
virtual ~SingletonFactory() {}
_TSingleton* create()
{
if (isCreated)
return _TSingleton::mp_Instance.get();
isCreated = true;
_TSingleton* pTmp = new _TSingleton();
_TSingleton::mp_Instance.reset(pTmp);
return _TSingleton::mp_Instance.get();
}
template <typename _TParam>
_TSingleton* create(_TParam param)
{
if (isCreated)
return _TSingleton::mp_Instance.get();
isCreated = true;
_TSingleton* pTmp = new _TSingleton(param);
_TSingleton::mp_Instance.reset(pTmp);
return _TSingleton::mp_Instance.get();
}
template <typename _TP1, typename _TP2>
_TSingleton* create(_TP1 p1, _TP2 p2)
{
if (isCreated)
return _TSingleton::mp_Instance.get();
isCreated = true;
_TSingleton* pTmp = new _TSingleton(p1, p2);
_TSingleton::mp_Instance.reset(pTmp);
return _TSingleton::mp_Instance.get();
}
template <typename _TP1, typename _TP2, typename _TP3>
_TSingleton* create(_TP1 p1, _TP2 p2, _TP3 p3)
{
if (isCreated)
return _TSingleton::mp_Instance.get();
isCreated = true;
_TSingleton* pTmp = new _TSingleton(p1, p2, p3);
_TSingleton::mp_Instance.reset(pTmp);
return _TSingleton::mp_Instance.get();
}
template <typename _TP1, typename _TP2, typename _TP3, typename _TP4>
_TSingleton* create(_TP1 p1, _TP2 p2, _TP3 p3, _TP4 p4)
{
if (isCreated)
return _TSingleton::mp_Instance.get();
isCreated = true;
_TSingleton* pTmp = new _TSingleton(p1, p2, p3, p4);
_TSingleton::mp_Instance.reset(pTmp);
return _TSingleton::mp_Instance.get();
}
template <typename _TP1, typename _TP2, typename _TP3,
typename _TP4, typename _TP5>
_TSingleton* create(_TP1 p1, _TP2 p2, _TP3 p3, _TP4 p4, _TP5 p5)
{
if (isCreated)
return _TSingleton::mp_Instance.get();
isCreated = true;
_TSingleton* pTmp = new _TSingleton(p1, p2, p3, p4, p5);
_TSingleton::mp_Instance.reset(pTmp);
return _TSingleton::mp_Instance.get();
}
template <typename _TP1, typename _TP2, typename _TP3,
typename _TP4, typename _TP5, typename _TP6>
_TSingleton* create(_TP1 p1, _TP2 p2, _TP3 p3, _TP4 p4, _TP5 p5, _TP6 p6)
{
if (isCreated)
return _TSingleton::mp_Instance.get();
isCreated = true;
_TSingleton* pTmp = new _TSingleton(p1, p2, p3, p4, p5, p6);
_TSingleton::mp_Instance.reset(pTmp);
return _TSingleton::mp_Instance.get();
}
template <typename _TP1, typename _TP2, typename _TP3,
typename _TP4, typename _TP5, typename _TP6, typename _TP7>
_TSingleton* create(_TP1 p1, _TP2 p2, _TP3 p3, _TP4 p4, _TP5 p5, _TP6 p6, _TP7 p7)
{
if (isCreated)
return _TSingleton::mp_Instance.get();
isCreated = true;
_TSingleton* pTmp = new _TSingleton(p1, p2, p3, p4, p5, p6, p7);
_TSingleton::mp_Instance.reset(pTmp);
return _TSingleton::mp_Instance.get();
}
template <typename _TP1, typename _TP2, typename _TP3,
typename _TP4, typename _TP5, typename _TP6, typename _TP7, typename _TP8>
_TSingleton* create(_TP1 p1, _TP2 p2, _TP3 p3, _TP4 p4,
_TP5 p5, _TP6 p6, _TP7 p7, _TP8 p8)
{
if (isCreated)
return _TSingleton::mp_Instance.get();
isCreated = true;
_TSingleton* pTmp = new _TSingleton(p1, p2, p3, p4, p5, p6, p7, p8);
_TSingleton::mp_Instance.reset(pTmp);
return _TSingleton::mp_Instance.get();
}
void destroy()
{
_TSingleton::mp_Instance.reset();
isCreated = false;
}
private:
static bool isCreated;
};
template <typename _TSingleton> bool SingletonFactory<_TSingleton>::isCreated = false;
It can handle up to 8 constructor parameters. You can write more if you need.
Final Step
The MySingleton
class is more simple than ever:
class NonCopyable
{
protected:
NonCopyable(const NonCopyable& other) {}
NonCopyable& operator=(const NonCopyable& other) { return *this; }
};
class MySingleton : private NonCopyable, public Singleton<MySingleton>
{
friend class SingletonFactory<MySingleton>; private:
MySingleton(int foo) : m_Foo(foo) {}
private:
int m_Foo;
};
#ifdef SINGLETON_COMPILE_TIME_CHECK
template <> std::auto_ptr<MySingleton> Singleton<MySingleton>::mp_Instance =
std::auto_ptr<MySingleton>(SingletonFactory<MySingleton>().create(15));
#endif
That's it, the private
inheritance is guarantee that nobody can inherit this class.
If you want to initialize at compile time, just define the SINGLETON_COMPILE_TIME_CHECK
macro and initialize the mp_Instance
as you see above. If you don't know the initialization values at compile time, you have to call the SingletonFactory<_TSingleton>::create()
function in your code.