Let's Talk About... Singleton

From EDM2
Jump to: navigation, search

Written by Stefan Ruck

When I read about the Singleton design pattern (see [1]) for the first time, I was really enthusiastic about it. But when I started to use it, some doubts occurred to me. These are the results of my reflections.

What's A Singleton?

The main intention of a Singleton class is to make sure to have only one instance of this class with a global point of access to it, thus avoiding the use of global variables.

Gamma et al gave a printer spooler as an example. There may be many printers defined in an operating system, but there should only be one printer spooler.

I will use the Singleton design pattern in conjunction with initialization files (see [2]). In any program you need just one instance of your initialization file. So why not design the initialization file class as a Singleton. Therefore I will expand the sample program of [2] later on.

The declaration of a typical Singleton class is as follows:

  class Singleton
  {
    public:
      static Singleton * instance ();

    protected:
      Singleton ();

    private:
      static Singleton * m_pInstance;
  };

Figure 1: Declaration of a Singleton class

Its implementation looks like this:

  Singleton * Singleton :: m_pInstance = 0L;

  Singleton * Singleton :: instance ()

  {
    return ((m_pInstance ? m_pInstance
        : (m_pInstance = new Singleton)));
  }

Figure 2: Implementation of a Singleton class

Because the constructor is not of interest, it's not shown here.

Some explanation to those of you who aren't familiar with the ? operator. In spoken words the code in Singleton::instance is:

If m_pInstance is not null, return m_pInstance, else assign the address of the new allocated Singleton object to m_pInstance and return m_pInstance.

Like it or not, it works fine (and I like it a lot).

What Is The Problem?

Maybe you will see there is no way to delete the Singleton object created during the first call of Singleton::instance.

When you use a Singleton class e.g. in a Microsoft Visual C++ 4.x environment in debug mode, you will get a warning about memory leaks when you close the debugger. That's correct because there is no corresponding delete to the new in Singleton::instance.

OK, you can say: Why bother, as soon as the program terminates, all allocated memory will be released.

I think it's not a good programming style when there is no delete to a new. So using Singleton in the original manner, it seems to be incomplete to me.

So what can we do?

Three Steps To Heaven

1st Step: Singleton::destroyInstance

The implementation of Singleton::destroyInstance is quite easy.

  public:
    static void destroyInstance ();

  void Singleton :: destroyInstance ()

  {
    delete m_pInstance;

    m_pInstance = 0L;
  }

Figure 3: Declaration and implementation of Singleton::destroyInstance

The question is: When do we call Singleton::destroyInstance? The safest place to call it is inside of the destructor of the main application class. This works fine as long as you remember not to use it somewhere else and none of your colleagues is going to use this class. But as soon as you call it somewhere else, you can't be sure you won't destroy the instance that's used by another class which is storing the pointer returned by Singleton::instance in a class member.

So I think this isn't a safe way to destroy the allocated instance.

2nd Step: Reference Counting

What we can do is to increment a counter any time Singleton::instance is called and decrement it when Singleton::destroyInstance is called. Only when the counter is zero, will the instance be deleted. This makes sure the Singleton object is destroyed only when it is not used any more. And because destroyInstance isn't the right name any more, I will call it releaseInstance from now on.

This leads to the following changes:

  public:
    static void releaseInstance ();

  private:
    static unsigned long m_ulReferenceCounter;

  unsigned long Singleton :: m_ulReferenceCounter = 0L;

  Singleton * Singleton :: instance ()

  {
    if (!m_pInstance)
        m_pInstance = new Singleton;

    ++m_ulReferenceCounter;

    return (m_pInstance);
  }

Figure 4a: Changes in Singleton for reference counting

Singleton::instance increases Singleton::m_ulReferenceCounter only when the new operator is passed. It may be possible that an exception is thrown by the constructor of Singleton. In this case the counter should not be incremented.

  void Singleton :: releaseInstance ()
  {
    if (!m_ulReferenceCounter) // no instance?
        return;                // yes, return

    --m_ulReferenceCounter;

    if (m_ulReferenceCounter) // is the instance still in use?
        return;               // yes, don't destroy it

    delete m_pInstance;

    m_pInstance = 0L;
  }

Figure 4b: Changes in Singleton for reference counting

This is much better now. Anyone who uses the Singleton instance can be sure to have a valid pointer to it. Really? Can you always be sure? What happens when Singleton::releaseInstance is called two times by the same user? Right, Singleton::m_ulReferenceCounter is decreased and maybe this time, maybe sometime later, the instance is destroyed although it is in use.

Or maybe one user 'forgets' to call Singleton::releaseInstance. So the object will stay alive until the end of the days (here: the program).

These thoughts lead me to the final step.

3rd Step: A Singleton Smart Template Class

The goal is to be sure to have a legal pointer to the instance of a Singleton.

I thought about it a while and came to the conclusion that as long as you deal with the Singleton class itself, you can't protect the class's users from faulty usage. My solution is a helper template class which I will call a Singleton Smart Template, in short term SingletonST. It's designed almost like smart pointers described by Scott Meyers (see [3]).

The SingletonST class is the only class which is able to call Singleton::instance and Singleton::releaseInstance. Together with the reference counting described in step 2, it is a safe way to use Singleton. Any Singleton interface calls are done using the SingletonST object.

One really important thing to maintain the goal is to define a copy constructor for the SingletonST and the Singleton class. By default, it will be defined as memberwise assignment and memberwise initialization of the class (see [4]). This seems to be OK at first. But looking at it a little bit closer, you will see it is not.

  template <class T> class SingletonST
  {
    public:
      SingletonST ();
      ~SingletonST ();

      SingletonST <T> & operator = (const SingletonST  & rSource);

      T * operator -> () const;

      T&  operator * () const;

    private:
      T * m_pInstance;
  };

  template <class T>
  SingletonST <T> :: SingletonST ()
        : m_pInstance (T :: instance ())
  {
  }

  template <class T>
  SingletonST <T> :: ~SingletonST ()

  {
    m_pInstance -> releaseInstance ();
  }

  template <class T>
  T * ASingletonST <T> :: operator -> () const

  {
    return (m_pInstance);
  }

  template <class T>
  T& ASingletonST <T> :: operator * () const

  {
    return (*m_pInstance);
  }

  void f ()

  {
    SingletonST <Singlton> * A = new SingletonST <Singlton>;

    SingletonST B (*A);

    Singleton C (*B);

    delete A;
  }

Figure 5: Default copy constructor sample

What happens during the execution of function f()?

When the SingletonST A is created, Singleton::instance is called by SingletonST's constructor, and Singleton::m_ulReferenceCounter is increased. At the creation of SingletonST B B::m_pInstance is assigned to the value of A::m_pInstance. Singleton::instance is not called and therefore Singleton::m_ulReferenceCounter is not increased. And we have also the ability to create the Singlton object C directly by using the copy constructor. The value of Singleton::m_ulReferenceCounter remains one although we have two references to Singleton::m_pInstance (A::m_pInstance, B::m_pInstance). And we do have more than one instance of Singleton (Singleton::m_pInstance, C).

When A is deleted, Singleton::releaseInstance is called. Because Singleton::m_ulReferenceCounter is 1, the instance of Singleton will be deleted too. But B::m_pInstance still points to the no longer existing Singleton object As soon as you try to use B to access the Singleton object now, the program will crash. [Maybe not on the first access. Maybe only a little bit later after the memory you deleted has been reallocated. These bugs are really hard to find, so it is wise to do everything you can to avoid them. Ed]

Now the sample shows us that neither SingletonST nor Singleton should use the default copy constructor. Using SingletonST's default copy constructor leads to wrong reference count. Singleton's default copy constructor makes the Singleton class itself worthless. Remember, we use Singleton to have just one and only one instance of this class.

To avoid this scenario, we have to define a copy constructor for both classes ourselves. And because there should not be any Singleton object created directly, the copy constructor of Singleton is declared as private.

Don't worry about the dereferencing * operator in this sample. This means a reference of Singleton is returned by B. You can also use A here by typing Singleton C (**A);, which first returns the value of A (which itself is a pointer), here a SingletonST object, and this SingletonST object returns a reference to a Singleton object then.

I do define an empty assignment operator for both classes too. Since SingletonST::m_pInstance is the only data member (which is initialized at creation time of SingletonST) and has to point to the same Singleton object over all SingletonST instances, there is no need to assign anything. And because there is only one instance of Singleton, an assignment operator is not needed for this class. So Singleton's assignment operator is declared a private to prevent access to it.

This is the result:

  template <class T> class SingletonST : public IBase
  {
    public:
      SingletonST ();
      SingletonST (const SingletonST <T> & rSource);
      virtual ~SingletonST ();

      SingletonST <T> & operator = (const SingletonST <T> &
            rSource);

      T * operator -> () const;

      T&  operator * () const;

    private:
      T * m_pInstance;
  };

  SingletonST :: SingletonST ()
    : m_pInstance (Singleton :: instance ())

  {
  }

  SingletonST :: SingletonST (const SingletonST& rSource)
    : m_pInstance (Singleton :: instance ())

  {
  }

  SingletonST :: ~SingletonST ()

  {
    m_pInstance -> releaseInstance ();
  }

  SingletonST& SingletonST :: operator = (const SingletonST& rSource)

  {
    return (*this);
  }

  template <class T>
  T * SingletonST <T> :: operator -> () const

  {
    return (m_pInstance);
  }

  template <class T>
  T& SingletonST <T> :: operator * () const

  {
    return (*m_pInstance);
  }


  class Singleton

  {
    public:
      void Method ();

    protected:
      Singleton ();

    private:
      Singleton (const Singleton& rSource)  { }

      Singleton& operator = (const Singleton& rSource) { }

      static Singleton * instance ();
      static void releaseInstance ();

      static Singleton * m_pInstance;
      static unsigned long m_ulReferenceCounter;

      friend SingletonST <Singleton> :: SingletonST ();
      friend SingletonST <Singleton> :: SingletonST
                (const SingletonST&);
      friend SingletonST <Singleton> :: ~SingletonST ();
  };

Figure 6: SingletonST class and final declaration of Singleton

The methods Singleton::instance and Singleton::releaseInstance stay unchanged from the 2nd step. Singleton::Method is defined for demonstration purpose only.

The only way to access Singleton's instance now is using a SingletonST object. Any time a SingletonST object is created, Singleton::instance is called. This makes sure an instance of Singleton is present when needed. Any time a SingletonST object runs out of scope, Singleton::releaseInstance is called. When the last SingletonST object is about to be destroyed, the Singleton object will also be. So the disadvantage seen after the 2nd step is eliminated.

Because the methods to create and release the Singleton object are only accessible by the constructors / destructor of SingletonST, they cannot be called when they should not be called; this is especially important for Singleton::releaseInstance.

Singleton's copy constructor and assignment operator are defined private to disable the access to them.

The copy constructor of SingletonST is equal to the default constructor. Since the only data member of SingletonST is m_pInstance, which must not be copied to keep the reference counter of Singleton up to date, nothing needs to be copied here. Instead Singleton::instance is called to initialize SingletonST::m_pInstance and to be sure Singleton::m_ulReferenceCounter has the right value.

What I'm not looking at here is the case SingletonST is created by the new operator and not deleted. I think there is always a way to bypass the security built into classes.

The Advantages

I think the advantages of this solution are clear. The users of SingletonST can always be sure to have a valid access to Singleton. And they can always be sure that any memory allocated during run time is cleaned up.

The Disadvantages

Since the SingletonST has to be defined only once for all singleton you'll ever use, I can't see any disadvantages.

If you need implicit type conversion and use C Set++ or VAC++, you need to do some work, because none of them supports member function templates, as far as I've tested it.

When your compiler supports it, you just have to add the following member function to SingletonST

  template <class newType>
  operator SingletonST <newType>
    {
    return (SingletonST <newType> (m_pInstance));
    }

Figure 7: Member function template for implicit type conversion

All this stuff is perfectly discussed by Scott Meyers (see [5]).

The Sample Application

AIniConfig is defined in inicfg.hpp and inicfg.cpp, ASingletonST is defined in singlest.h and singlest.c.

For the sample program of this article I decided to use the source code of my INI file article (see [2]) as base. The original program saves its size and position into INI files. Here, I'm not using the AConfigFile and ACfgConfig classes, although ACfgConfig could be designed as a singleton too.

I've added the ability to save the size to AIniConfig and re-designed it as a singleton class.

This is what it looks like now:

  class AIniConfig : public AIniFile
  {
    public:
      Boolean isPositionSaved () const;
    // is a value for the position in the profile?

      POINTL getPosition () const;
    // retrieve the position

      AIniConfig& setPosition (const POINTL pPosition);
    // save the position

      Boolean isSizeSaved () const;
    // is a value for the size in the profile?

      SIZEL getSize () const;
    // retrieve the size

      AIniConfig& setSize (const SIZEL sSize);
    // save the size

    protected:
      AIniConfig () { }

    private:
      AIniConfig (const AIniConfig& rSource) { }

      AIniConfig & operator = (const AIniConfig& rSource)
      { return (*this); }

      static AIniConfig * instance ();
    // return an instance

      static void releaseInstance ();
    // release an instance

      static AIniConfig * m_pInstance;
    // pointer to the single instance

      static unsigned long m_ulReferenceCounter;
    // counts the instance requests

      static const char * const m_pszPosition;
    // position key name
      static const char * const m_pszSize;
    // size key name

      friend ASingletonST <AIniConfig> :: ASingletonST ();
      friend ASingletonST <AIniConfig> :: ASingletonST
        (const ASingletonST <AIniConfig> &);
      friend ASingletonST <AIniConfig> :: ~ASingletonST ();
  };

Figure 8: Declaration of AIniConfig as a singleton class

As you can see above, it isn't AIniFile, the base class of AIniConfig, that is changed into a singleton class, but AIniConfig.

In my opinion it doesn't make much sense to design a base class as a singleton, although it is described by Gamma et al. I really don't like the way they suggested to decide which class is to be created when the Singleton::instance method of a base class is called (using an environment variable). As soon as the application's user deletes the setting of this variable, the whole program crashes and you will search a long time for the reason. Another point is as soon as a new derived class arises, you have to change the base class too. Passing a parameter to the base class's instance method will eliminate the environment variable problem, but not the need for changes in the base class when you create a new derived class.

The new class, ASingletonST, is the only way to access the initialization file now. It looks exactly like SingletonST described above.

To prove that all instances of ASingletonST work with the same instance of AIniConfig, the sample has two ASingletonST objects. One reads the size and saves the position and the other reads the position and writes the size of the window. This is only for demonstration purposes. In real life, it is not very useful.

When you run the sample, be sure to close it using the F3 key or the menu 'File' 'Exit'. Any other way to end it doesn't save the current position and size of the window.

When you build the sample application, you can use the makefiles included in the source zip. There is one makefile for IBM's C Set++ (singletn.mac, not a MAC file) and one for IBM's Visual Age for C++ (singletn.mav). If you decide to use your own makefile, make sure all switches especially for the use of templates are set correctly.

Wanted: Your Opinion

Now these are my very personal thoughts and conclusions on the Singleton design pattern. As said above, I do not agree with Gamma et al in all points of their design.

A colleague of mine mentioned it is not worth it to build a class around the singleton. On the other hand a friend was really interested in my idea. So what do you think? Please give me your feed back on my e-mail address 100664.1353@compuserve.com. If there is an interest, I will write a summary of the discussion later on.

Acknowledgement

I'd like to thank Farhad Fouladi who recommended me to buy 'Design Patterns' and drew my attention to the copy constructor and assignment operator. Many thanks also to my girlfriend Elke Ungeheuer who re-read this article like all the others although she's not a programmer (and absolutely not interested in this stuff).

Last but not least thanks to the folks of EDM/2 for final editing.

[1] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides; Design Patterns, 1995, p. 127ff

[2] Stefan Ruck, Manage Your Configuration Files and Data, EDM/2 volume 5 issue 9, 1997

[3] Scott Meyers, More Effective C++, 1996, p. 159ff

[4] Bjarne Stroustrup, The C++ Programming Language, Second Edition, 1995, p. 582

[5] Scott Meyers, More Effective C++, 1996, p. 173ff