Skip to main content
gamedev.net
🔒 Locked

Singleton problems

Started by Skute Aug 31, 2005 at 3:57 AM 21 replies 2.8k views
Original Post
Skute
Skute
Hi, im having a little trouble with writing a simple singleton. My implementation is as follows:

// Singleton.h
#pragma once

template <class T>
class CSingleton
{
  public:
    static T* CreateInstance(void);
    static void DeleteInstance(void);

    static T* GetInstancePtr(void);
    static T& GetInstanceRef(void);
    
  private:
    CSingleton(void);
    CSingleton(const CSingleton& rCopy);
    CSingleton& operator=(const CSingleton& rCopy);
    ~CSingleton(void);

    static T* m_pT;
};

template <class T>
T* CSingleton<T>::m_pT = NULL;

template <class T>
CSingleton<T>::CSingleton(void)
{
}

template <class T>
CSingleton<T>::~CSingleton(void)
{
}

template <class T>
T* CSingleton<T>::CreateInstance(void)
{
  if (m_pT == NULL)
    m_pT = new T();

  return m_pT;
}

template <class T>
void CSingleton<T>::DeleteInstance(void)
{
  if (m_pT != NULL)
  {
    delete m_pT;
    m_pT = NULL;
  }
}

template <class T>
T* CSingleton<T>::GetInstancePtr(void)
{
  return m_pT;
}

template <class T>
T& CSingleton<T>::GetInstanceRef(void)
{
  return *m_pT;
}

But it has lead to problems when trying to make a logging class a singleton:

// Log.h
#pragma once

class CLog : public CSingleton<CLog>
{
  public:
    CLog(void);
    ~CLog(void);
};

// Engine.cpp
...
  CLog* pLog = CLog::GetInstancePtr();
  CLog* pLog2 = new CLog(); // Test to make sure that the user cant make a new object
...

The compiler moans that it cant access the private members __ctor and dtor in the CLogs constructor and destructor. Which obviously makes sense, but if i dont have them as private, the developer is able to call "new CLog()" which means it breaks the singleton rules. Can anyone give me any pointers where im going wrong?
Mark Ingramhttp://www.mark-ingram.com
Illco
Illco
So basically you took precautions for not allowing the user to create instances of the class using new. Subsequently you tried to create a new instance using new and the compiler complains.

I'd say all is fine! In more detail: you don't want to expose the constructor of the derived class either.

Greetz,

Illco
Skute
Skute
Yeah thats the thing, i dont really want to mess around with all the child classes, i want to disable the user using new on the class without changing the original implementation except for inheriting from CSingleton.

BTW: The compiler complains from within the CLog() & ~CLog() saying it cant access the CSingleton constructor / destructor. But this *isnt* as a result of the code in Engine.cpp - its just as it tries to compile log.cpp.
Mark Ingramhttp://www.mark-ingram.com
Illco
Illco
I know. If the parent constructor is private you cannot call it in a derived class. I know see your problem appear: you want to forbid the user to even derive such a class or, on the other hand, make sure its constructors are indeed private. I'm not into templates enough to help you there; though I doubt it is possible to create a nice singleton base class using templates.
Rebooted
Rebooted
You need to hide your constructors, and then make CSingleton a friend class, so it can access them.
Illco
Illco
That would work but defeats the purpose as it requires the templated base class to know about its inheritors.
Skute
Skute
yeah, that would be a real pain having a whole list of friend classes inside the singleton template. ive resorted to using the following technique:

#define CLASS_SINGLETON(CLASS) CLASS : public CSingleton<CLASS>#define IMPLEMENT_SINGLETON(CLASS)   friend CSingleton<CLASS>;   private:     CLASS(void);     CLASS(const CLASS&);     CLASS& operator=(const CLASS&);     ~CLASS(void);


...and...
class CLASS_SINGLETON(CLog){  IMPLEMENT_SINGLETON(CLog)};


But im a bit dubious about using a define to create constructors for a class - it might not be very clear - is it better to just write them all out by hand each time?
Mark Ingramhttp://www.mark-ingram.com
Illco
Illco
OMG. In my opninion? Definitely.
Nitage
Nitage
template<class T>class singleton{public:    static T& instance()    {        if(!_instance)        {            _instance = new T;        }        return _instance;    }private:    static T* _instance;};


Then inherit your derviced class D from singleton to make it a singleton.

However, the derived class must make its constructor private and must make singleton a friend class. There is no way around this. The above is as clean as you're going to get.

NOTE:
The above is just a skeleton. Don't use it as is.
Skute
Skute
ok, ive rewritten it slightly:

// Singleton.h#pragma oncetemplate <class T>class CSingleton{  public:    static T* GetInstancePtr(void);    static T& GetInstanceRef(void);      protected:    CSingleton(void);    ~CSingleton(void);  private:    CSingleton(const CSingleton& rCopy);    CSingleton& operator=(const CSingleton& rCopy);    static T* CreateInstance(void);    static void DeleteInstance(void);    static T* m_pT;};template <class T>T* CSingleton<T>::m_pT = NULL;template <class T>CSingleton<T>::CSingleton(void){}template <class T>CSingleton<T>::~CSingleton(void){}template <class T>T* CSingleton<T>::CreateInstance(void){  if (m_pT == NULL)  {    m_pT = new T();    atexit(CSingleton<T>::DeleteInstance);  }  return m_pT;}template <class T>void CSingleton<T>::DeleteInstance(void){  if (m_pT != NULL)  {    delete m_pT;    m_pT = NULL;  }}template <class T>T* CSingleton<T>::GetInstancePtr(void){  if (m_pT == NULL)    CSingleton<T>::CreateInstance();  return m_pT;}template <class T>T& CSingleton<T>::GetInstanceRef(void){  if (m_pT == NULL)    CSingleton<T>::CreateInstance();  return *m_pT;}


// Log.h#pragma onceclass CLog : public CSingleton<CLog>{  friend CSingleton<CLog>;  private:    CLog(void);    CLog(const CLog&);    CLog& operator=(const CLog&);    ~CLog(void);};// Engine.cpp...  CLog* pLog = CLog::GetInstancePtr();  //CLog* pLog2 = new CLog(); // This doesnt work - yay!...


Any advances on this? Seems to work, no memory leaks or anything, memory is cleared up during atexit().
Mark Ingramhttp://www.mark-ingram.com
Nitage
Nitage
You could use a std::auto_ptr to hold the instance. That way you don't have to use atexit().

Skute
Skute
from what i read that is a bad / not so good idea as you have no idea when that pointer gets free'd? at least using the atexit() function will free it last - after all other global objects? (or am i getting confused...)

i was trying to get around the destruction order problem of:

class A init -> class Log init -> class B init | CRASH | class B del -> class Log del -> class A del.

As the log class gets deleted before class A does, when GetInstance() is called, it will recreate another object? whereas if you use the atexit function it will be deleted after all the other objects?



Mark Ingramhttp://www.mark-ingram.com
Rebooted
Rebooted
Quote:
Original post by Illco
That would work but defeats the purpose as it requires the templated base class to know about its inheritors.

No it doesn't.

Skute
Skute
i do need to make CSingleton a friend class of CLog because it cant call new on it (because the ctor + dtor are private).
Mark Ingramhttp://www.mark-ingram.com
Illco
Illco
No Rebooted is right in correcting me -- I thought at first he meant making the log class a friend of the base class but that was not what he had written. It is the other way around.

I still think all this trouble isn't worth it -- the singleton pattern is so simple and since you still end up having to guard it yourself using protected or private constructors it seems like a waste to me.

Illco
Rebooted
Rebooted
When I use a singleton, which is rare, I use Loki::SingletonHolder, and don't forcibly stop users from directly creating an instance of the class, just advise them against it. This separates the singleton pattern from the class itself. I'd recommend Loki's SingletonHolder for a real project.
Skute
Skute
Surely if the user can create an object by using new() or creating on the stack then it isnt a true singleton?
Mark Ingramhttp://www.mark-ingram.com
Rebooted
Rebooted
Well if you want to make your constructors private just make the appropriate CreationPolicy class a friend. ie: Loki::CreateUsingNew.
garyfletcher
garyfletcher
Someone said that using templates could not impletemnt singeltons . I do not know where this came from as:

[source}template<typename T>class Singleton{   static T* ms_singleton;   public:      Singleton()      {         assert(!ms_singleton);         //use a cunning trick to get the singleton pointing to the start of         //the whole, rather than the start of the Singleton part of the object         int offset = (int)(T*)1 - (int)(Singleton <T>*)(T*)1;         ms_singleton = (T*)((int)this + offset);      }      ~Singleton()      {         assert(ms_singleton);         ms_singleton=0;      }      static T& GetSingleton()      {         assert(ms_singleton);         return *ms_singleton;      }      static T* GetSingletonPtr()      {         assert(ms_singleton);         return ms_singleton;      }};template <typename T> T* Singleton <T>::ms_singleton = 0;[/source}


Now igonre the cunning bit!!!! Singleton, template!!! Now if you want to derive from it:

To use the singleton class, we derive a class SomeClass from

Singleton.

One thing to note about this type of singleton is that we - not the loader - are responsible for creating the singleton and destroying it again when we're done. We create it simply by calling new SomeClass() somewhere in code - the constructor takes care of the rest, so we don't even need to store the pointer that new returns. To destroy it, we call delete SomeClass::GetSingletonPtr(); that also sets the singleton pointer back to zero, so we can recreate the singleton if we want.

Enginuity - Game dev (Superpig)

Am sorry but it works..:) Or read Andrei Alexandrecu (Modern C++ Design) which gives many ipmlementtions for a singleton...the easiest being the phoenix.
Gary.Goodbye, and thanks for all the fish.
Deyja
Deyja
A singleton is just a global with a funny name that makes you feel somewhat less queasy about using it. Generally, the monostate pattern is much clearer, and much less verbose.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.