Der Singleton ist ein praktisches Entwurfsmuster in der Programmierung. Falsch benutzt bringt der Singleton jedoch auch einige Probleme in sich.
Dieser Artikel diskutiert verschiedene Möglichkeiten bei der Implementierung von Singletons sowie deren Vor und Nachteile.
Zunächst stellen wir uns die folgende Klasse vor, die nur aus einer Funktion sowie einem Konstruktor und einem Destruktor besteht.
/**
The dummy class
*/
class CDummy
{
public:
CDummy(void) { OutputDebugString( L"Hello Dummy\n" ); }
~CDummy(void) { OutputDebugString( L"Bye bye Dummy\n" ); }
void DoSomething() { OutputDebugString( L"Did something\n" ); }
};
Diese Klasse soll nun ein Singleton werden. Dazu wird zunächst der Konstruktor sowie der Destruktor private gemacht, sodass von dieser Klasse keinen Instanzen mehr mit new() erstellt oder mittels delete() gelöscht werden können.
Folgendes, relativ häufig angewendetes Beispiel implementiert die Singletoninstanz als statischen Zeiger innerhalb der Singletonklasse. Bei jedem Aufruf der getInstance() Methode wird der Instanzzeiger auf NULL geprüft und ggf. eine neue Instanz erstellt. Um die Instanz wieder frei zu geben, wird eine release() Methode implementiert, in welcher die Instanz gelöscht und der Zeiger wieder auf NULL gesetzt wird. Durch mehrere gemischte Aufrufe von getInstance() und release() entsteht somit ein gewisser Overhead. Außerdem kann es bei bestimmten Anwendungsgebieten (zB. Cache-Klassen) zu ungewolltem verhalten führen. Wird diese release() Methode nach der Verwendung der Klasse gar nicht aufgerufen, bleibt die Singletoninstanz am Ende der Programmlaufzeit übrig und der Destruktor wird nicht durchlaufen. Da im Destruktor wichtiger Code stehen kann, ist also Disziplin im Umgang mit einer auf dieser Weise implementierten Singletonklasse gefordert.
Das Verhalten, die Instanz erst zu erzeugen, wenn sie benötigt wird, nennt man ‚Lazy Creation‘. Der Nachteil an Lazy Creation ist, dass der erste Aufruf von getInstance() langsamer abläuft als die Folgeaufrufe.
class CDummy
{
public:
static CDummy * getInstance();
static void release();
void DoSomething() { OutputDebugString( L"Did something\n" ); }
private:
CDummy(void) { OutputDebugString( L"Hello Dummy\n" ); }
~CDummy(void) { OutputDebugString( L"Bye bye Dummy\n" ); }
static CDummy *theInstance;
};
CDummy* CDummy::theInstance = NULL;
CDummy* CDummy::getInstance()
{
if( theInstance == NULL )
theInstance = new CDummy();
return theInstance;
}
void CDummy::release()
{
if( theInstance != NULL )
delete theInstance;
theInstance = NULL;
}
Ein anderer Initialisierungsansatz genannt ‚Eager Creation‘ bietet den Vorteil, dass auch der erste Aufruf von getInstance() zügig abläuft, da die Singletoninstanz bereits im Vorlauf (beim Initialisieren der Anwendung / DLL) erzeugt wird. Wird die Singletonklasse jedoch niemals verwendet wurde die Singletoninstanz umsonst erzeugt. Der release() Aufruf ist in jedem Fall erforderlich.
Um das oben stehende Beispiel auf Eager Creation umzustellen reicht es aus, eine Zeile anzupassen.
CDummy* CDummy::theInstance = new CDummy();
Das folgende Beispiel, welches ich persönlich – sofern möglich – bevorzuge, realisiert die gleiche Aufgabe wie das obige Beispiel, jedoch mit weniger Code und einigen Vorteilen gegen über den obigen Varianten. 😉
class CDummy
{
private:
CDummy(void) { OutputDebugString( L"Hello Dummy\n" ); }
~CDummy(void) { OutputDebugString( L"Bye bye Dummy\n" ); }
public:
void DoSomething() { OutputDebugString( L"Did something\n" ); }
static CDummy *getInstance();
};
CDummy* CDummy::getInstance() {
static CDummy theInstance;
return &theInstance;
}
Beachten Sie bitte, dass die Singletoninstanz nicht als Member der Klasse definiert ist, sondern direkt in der Methode getInstance() definiert wird. Dies hat zum einen zur Folge, dass die Singletoninstanz erst erzeugt wird, wenn die Funktion getInstance() erstmals aufgerufen wird (Lazy Creation). Zum anderen benötigt diese Singletonklasse keine release() Methode, da der Destruktor der Singletonklasse automatisch durchlaufen wird, sobald die Anwendung beendet wird. (Die statische / globale Variable wird durch einen dynamischen AtExit Destruktor zerstört.)
Hallo Herr Oette, das von Ihnen bevorzugte Singleton gefaellt mir auch ganz gut, mit der Ausnahme, dass es nicht kompilierbar ist. Der Konstruktor und der Destruktor sind private. Macht man das public, hat das den Nachteil, dass jemand auf die Idee kommen koennte, die Klasse so zu verwenden, dann ist der Ansatz mit einer statischen Methode doch besser:
class CDummy
{
private:
CDummy(void)
{
OutputDebugString(„Hello Dummy\n“);
}
public:
~CDummy(void)
{
OutputDebugString(„Bye bye Dummy\n“);
}
static CDummy& getExemplar()
{
static CDummy object;
return object;
}
void DoSomething()
{
OutputDebugString(„Did something\n“);
}
};
//aufruf
CDummy::getExemplar().DoSomething();
Hallo, mit welchem Compiler funktioniert der Code nicht?
Danke für das Gute Beispiel! 🙂 Bei mir funktioniert (kompiliert) das Beispiel wunderbar.
Interessant ist die explizite Löschung des Objekts. Soetwas gibt es in Java bspw. nur eingeschränkt. Hier habe ich einen Post gefunden, in dem in Java ein Singleton entworfen wird, das automatisiert gelöscht wird, sobald es nicht mehr verwendet wird: http://www.return-this.com/das-singleton-pattern-mit-lebenszeit/