Problem mit Singleton



  • Hallo,
    ich habe eine Klasse, die nur einmal existieren darf, und dessen Werte nur von einer anderen Klasse verändert werden dürfen.
    Um das zu erreichen habe ich solch ein Singleton konstruiert:

    // Header
    #ifndef WINDOWDIMENSION_H
    #define WINDOWDIMENSION_H
    
    class WindowDimension {
    public:
    	static WindowDimension* Get();
    	~WindowDimension();
    	int Width() const {return width;};
    	int Height() const {return height;};
    protected:
    	void SetDimension(int windowWidth, int windowHeight);
    	WindowDimension();
    private:
    	int width, height;
    	static WindowDimension* instance;
    
    };
    
    #endif
    
    // .cpp
    #include "WindowDimension.h"
    
    WindowDimension::WindowDimension() : width(0), height(0) {};
    
    WindowDimension::~WindowDimension() {
    	delete instance;
    	instance = 0;
    };
    
    WindowDimension* WindowDimension::Get() {
    	if (!instance)
    		instance = new WindowDimension;
    	return instance;
    };
    
    void WindowDimension::SetDimension(int windowWidth, int windowHeight) {
    	width = windowWidth;
    	height = windowHeight;
    }
    
    WindowDimension* WindowDimension::instance = nullptr;
    

    Die Klasse, die die Werte verändern darf erbt privat von der WindowDimension-Klasse.
    Dabei habe ich doch einige Probleme:
    1. Momentan rufe ich SetDimension so auf:

    WindowDimension::SetDimension(init.width, init.height);
    

    Dabei glaube ich aber selber, dass das Verhalten undefiniert ist. Mein Problem ist aber, dass solch ein Aufruf nicht funktioniert:

    WindowDimension::Get()->SetDimension(init.width, init.height);
    

    Folgender Compilerfehler kommt:

    error C2248: "WindowDimension::SetDimension": Kein Zugriff auf private Member, dessen Deklaration in der WindowDimension-Klasse erfolgte.
    

    Dabei ist SetDimension doch protected und sollte doch von einer Klasse, die davon privat erbt aufgeruft werden können, oder?

    2. Wenn ich die Width() oder Height() Funktion so aufrufe:

    WindowDimension::Get()->Width();
    // oder
    WindowDimension::Get()->Height();
    

    also ein Objekt erstelle wird im Destruktor von WindowDimension beim schließen des Programm folgende Exception geworfen:

    Eine Ausnahme (erste Chance) bei 0x010a1cf9 in Program.exe: 0xC00000FD: Stack overflow.
    Unbehandelte Ausnahme bei 0x010a1cf9 in Program.exe: 0xC00000FD: Stack overflow.
    

    Wieso habe ich hier auf einmal einen Stackoverflow?

    Danke schonmal für eure Hilfe 🙂



  • Vielleicht solltest du das statt über Vererbung lieber über friend lösen.

    p.s.

    Wieso habe ich hier auf einmal einen Stackoverflow?

    Weil er sich selbst rekursiv aufruft (über delete instance ).



  • Dir ist hoffentlich klar, daß du mit der (selbst privaten) Vererbung den Sinn deines Singleton untergräbst? Du kannst zwar keine weiteren WindowDimension-Objekte anlegen, aber Objekte deiner Steuerklasse - und die haben auch jeweils ein WindowDimension-Teilobjekt.
    Und nebenbei erzeugt der Destruktor eine Endlos-Rekursion - bei Freigabe des Kontroll-Objekts wird auch dessen WD-Anteil zerstört, dieser löscht die globale Instanz, wodurch du wieder im Destruktor landest.

    Was genau hast du denn mit dieser Kombination vor? Vielleicht ist es besser, die Zugriffsklasse nicht als Kind des Singleton anzulegen, sondern als friend.



  • Hi,

    wenn Du erbst, erzeugst Du ein zweites Singleton.

    Dabei ist SetDimension doch protected und sollte doch von einer Klasse, die davon privat erbt aufgeruft werden können, oder?

    Aber nur innerhalb Deiner Klasse. Hast Du Mal probiert, ohne das WindowDimension die Get-Methode aufzurufen?

    Den zweiten Fehler kann ich gerade nicht erklären. Aber die private Vererbung für deinen Zweck scheint nicht sehr sinnvoll. Was mir in erster Linie einleuchten würde, wäre die Klassen zusammenzuschieben oder ein friend anzubieten. Blöderweise könnte man durch das friend dann auch das Singleton mehrfach erzeugen. Deswegen könntest Du eine Zwischenklasse anbieten. Mediator könnte ein Freund von dem Singleton sein und ein paar private Methoden anbieten. Mediator hat dann wiederum deine verändernde Klasse als Freund, aber bietet nur die Zugriffsmethoden an, die die verändernde Klasse besitzen soll.

    Ist zwar auch etwas umständlich, aber die private Vererbung erscheint mir irgendwie nicht sinnvoll. Oder du schiebst das Singleton und die andere Klasse eben zusammen, vielleicht ergibt das Sinn.



  • Danke für eure Antworten.
    Also das mit dem rekursiven Destruktor habe ich jetzt verstanden aber wie krieg ich die Rekursion weg? Ein friend erscheint mir jetzt auch sinnvoller, aber wieso kann man mit einem friend ein Singleton mehrmals erzeugen und wann wird es mehrmals erzeugt?



  • Na ja, überlege Dir Mal, wieso man ein Singleton normalerweise nicht mehrfach erzeugen kann. Dann siehst Du, dass friend das aushebelt. Der verantwortliche Programmierer wird das natürlich auch nicht ausnutzen, aber die befreundete Klasse erhält grundsätzlich die Möglichkeit zum Mehrfacherzeugen.



  • Pikkolini schrieb:

    Danke für eure Antworten.
    Also das mit dem rekursiven Destruktor habe ich jetzt verstanden aber wie krieg ich die Rekursion weg?

    Die Rekursion kannst du normalerweise verhindern, indem du den rekursiven Funktionsaufruf weglässt - in deinem Fall ist das das delete instance; . Es gibt übrigens auch bessere Möglichkeiten, ein Singleton anzulegen als über den Heap (eine elegantere und stabilere Variante ist eine statische Variable innerhalb der get()-Methode)

    Ein friend erscheint mir jetzt auch sinnvoller, aber wieso kann man mit einem friend ein Singleton mehrmals erzeugen und wann wird es mehrmals erzeugt?

    Ein friend hat Zugriff auf die privaten Elemente der Klasse, also auch auf die Konstruktoren, die du so sorgfältig geschützt hast. Das heißt auch, daß du in einer Methode der friend-Klasse eine lokale Singleton-Instanz erzeugen könntest.
    (aber imho ist das nicht wirklich kritisch, schließlich gehören die Klassen sowieso zusammen und der Entwickler weiß selber, daß er sowas nicht machen sollte)


Anmelden zum Antworten