Tuesday 17 December 2013

Using the Singleton Pattern

We all know and love the singleton pattern, whereby you can ensure you only have one instantiated copy of a class, and can refer to that instance of the class at will?  Yes... Very useful for holding configuration if you're not totally familiar with the pattern.

In C++ we might have a singleton like this:

---- Singleton.hpp ----

#ifndef SINGLETON_CLASS
#define SINGLETON_CLASS

template <class C>
class Singleton
{

protected:
Singleton() {}
~Singleton() {}
private:
static C* s_Instance;
// Prevent copy
Singleton(Singleton&);
// Prevent assign
void operator=(Singleton&);

public:
inline static C& Instance()
{
if ( !s_Instance )
{
s_Instance = new C;
}
return *s_Instance;
}
inline static void DestroyInstance()
{
if ( s_Instance )
{
delete s_Instance;
s_Instance = NULL;
}
}
};

template <class C>
C* Singleton<C>::s_Instance = NULL;

#endif

----------

So this class gives us a basic singleton build into a class, so if we code:

-- Config.hpp --

#ifndef Config_CLASS
#define Config_CLASS

#include "Singleton.hpp"

#include <string>

// Forward declaration
class Config;

// Actual declaration
class Config : public Singleton<Config>
{
friend class Singleton<Config>;
private:
std::string m_Path;
/// Constructor is private
/// as the friend singleton
/// instantiates your class
/// with no params
Config ()
:
m_Path("C:\\HelloWorld")
{
}
public:
const std::string& Path()
{
return *m_Path;
}

};

#endif

-------------------

Nothing majorly wrong with this - I've not run this code through a compiler, to it just looks right and hopefully you get what I'm on about so far.

So our class derived from the singleton uses it, but each time we need to write a singleton we need to include this construction, and I've seen some people using macro's to replace that... so you might have:

#define SINGLETON_START (class_name) \
class class_name : public Singleton<class_name> \
{ \
friend class Singleton<class_name>;
And then:

#define SINGLETON_END() };

So forearmed we can now have our config look like this:

-- Config.hpp --

#ifndef Config_CLASS
#define Config_CLASS

#include "Singleton.hpp"

#include <string>

// Forward declaration
class Config;

SINGLETON_START(Config)
private:
std::string m_Path;
/// Constructor is private
/// as the friend singleton
/// instantiates your class
/// with no params
Config ()
:
m_Path("C:\\HelloWorld")
{
}
public:
const std::string& Path()
{
return *m_Path;
}

SINGLETON_END()

#endif

-------------------

Now, there's nothing wrong with this code-wise, its big crimes however are not obvious and in my opinion out way the benefit of using it.

So the benefits are the ease of declaring the inheritance and the friendship, it smooths that all out to a single line.

But the drawbacks, lets start with the plain text, code completion instantly breaks, it does not expand the Macro for the class definition.

Next, the physical act of using a Macro like this obfuscates away some code, and we're not talking about a trivial piece of maths we're talking about our class definition, so we instantly can't really use any of the decent document generators on our code, we could after using our own pre-processor to expand the macro ourselves, but its a real pain in the bum.

So this pattern is of use, and the macro can be of use, but using it is really a case of DO, or DO NOT... I'm on the DO NOT side, and I'll be adding this to my personal coding standards document soon.

No comments:

Post a Comment