C++-Programmierung: Entwurfsmuster: Singleton

Singleton

Bearbeiten

Ein Singleton stellt sicher, dass genau ein Objekt einer Klasse instantiiert wurde (vgl. WikiBook Muster). Außerdem wird durch Singletons das Verwalten von globalen Variablen, die den selben Zweck erfüllen können, vermieden.

Implementierung

Bearbeiten

Klassen, die das Singleton-Konzept implementieren, können entweder, wenn man nur wenige dieser Art braucht, immer neu geschrieben werden, oder auch mittels eines Templates generisch programmiert werden.

Ein Singleton wird instantiiert, wenn das erste Mal mit einer Funktion wie instance () ein Klassenobjekt angefordert wird. Ein Singleton, das dynamisch auf dem Heap-Speicher realisiert wurde, kann explizit zerstört werden, ansonsten existieren Singletons bis zum Programmende.

Normal (Heap)

Bearbeiten
 class N
 {
 public:
    static N* instance ()
    {
       static CGuard g;   // Speicherbereinigung
       if (!_instance)
          _instance = new N ();
       return _instance;
    }
    void xyz () {};
 private:
    static N* _instance;
    N () { } /* verhindert, dass ein Objekt von außerhalb von N erzeugt wird. */
              // protected, wenn man von der Klasse noch erben möchte
    N ( const N& ); /* verhindert, dass eine weitere Instanz via
 Kopie-Konstruktor erstellt werden kann */
    ~N () { }
    class CGuard
    {
    public:
       ~CGuard()
       {
          if( NULL != N::_instance )
          {
             delete N::_instance;
             N::_instance = NULL;
          }
       }
    };
 };
 N* N::_instance = 0; /* statische Elemente einer Klasse müssen initialisiert werden. Alternativ ist es auch möglich den Pointer mit nullptr zu initialisieren.*/
/* Verwendung */
N::instance ()->xyz ();

Normal (Stack)

Bearbeiten
 class N
 {
 public:
    static N& instance()
    {
       static N _instance;
       return _instance;
    }
    ~N() {}
    void xyz();
 private:
    N() {}           // verhindert, dass ein Objekt von außerhalb von N erzeugt wird.
                    // protected, wenn man von der Klasse noch erben möchte
    N( const N& ); /* verhindert, dass eine weitere Instanz via
 Kopier-Konstruktor erstellt werden kann */
    N & operator = (const N &); //Verhindert weitere Instanz durch Kopie
 };
/* Verwendung */
N& s = N::instance();
s.xyz();
//oder
N::instance().xyz();

Template (Heap)

Bearbeiten
 template <typename C>
 class Singleton
 {
 public:
    static C* instance ()
    {
       if (!_instance)
          _instance = new C ();
       return _instance;
    }
    virtual
    ~Singleton ()
    {
       _instance = 0;
    }
 private:
    static C* _instance;
 protected:
    Singleton () { }
 };
 template <typename C> C* Singleton <C>::_instance = 0;

/* Verwendung */
 class Test : public Singleton <Test>
 {
 friend class Singleton <Test>;
 public:
    ~Test () { }
    void xyz () { }
 protected:
    Test () { }
 };
 Test::instance ()->xyz ();

Eine Fehlerquelle ist das Weglassen des friend-Qualifizierers in der Klassendefinition abgeleiteter Klassen. Dieser Fehler wird jedoch vom Compiler während der Übersetzung offenbart, wenn der Constructor der abgeleiteten Klasse protected oder private deklariert wurde.

Makro (Stack)

Bearbeiten
 #define DEF_SINGLETON( NAME )    \
 public:                        \
    static NAME& instance()      \
    {                            \
       static NAME _instance;    \
       return _instance;         \
    }                            \
 private:                       \
    NAME();               \
    NAME( const NAME& );

 /* Verwendung */
 class N
 {
    DEF_SINGLETON( N )
 public:
    void xyz();
 };
 ...
 N& s = N::instance();
 s.xyz();


Implementierung mit Funktionen

Bearbeiten

Die Implementierung mithilfe von Klassen ist an Programmiersprachen wie bspw. Java entlehnt. In C++ (hier C++11) lässt sich dies auch mithilfe freistehender Funktionen implementieren:

template <typename T>
T& uniqueInstance()
{
  static thread_local T _instance;
  return _instance;
}

// Verwendungsbeispiel:
A& a = uniqueInstance<A>();
a.foo();
uniqueInstance<A>().foo();

Falls es die Möglichkeit geben soll, die bestehende Instanz durch eine andere auszutauschen, ist auch Folgendes möglich:

template <typename T>
T& uniqueInstance(T* new_instance = nullptr)
{
  static thread_local std::unique_ptr<T> _instance(new T());
  if(new_instance)
    _instance.reset(new_instance);
  
  return *_instance.get();
}

// Verwendungsbeispiel:
uniqueInstance<A>(new A(42));
uniqueInstance<A>().foo();