Globaler Zugriff auf Objekt einer abgeleiteten Klasse



  • Hallo,

    ich stoße gerade auf ein Problem und hoffe auf euren Rat, wie man es am elegantesten löst.

    Ich habe eine abstr. Basisklasse SchedChannelA mit einigen bereits implementierten Funktionen und einigen Membervariablen, u.a. m_NextCall. Von dieser werden mehrere Klassen abgeleitet. Von einigen dieser abgel. Klassen sollen mehrere Objekte erstellt werden können, aber es soll eine Klasse geben (SocketSrvChn), die nur einmal erstellt werden kann, und eine abstrakte Klasse (OutputChnA), von der andere Klassen abgeleitet werden, die auch nur einmal erstellt werden können (HtmlOutputChn, DataOutputChn, HeaderOutputChn).

    Nachdem ich in der obersten Basisklasse bereits einen öffentlichen Konstruktor mit mehreren Parametern angegeben habe, kann ich das Singleton-Entwurfsmuster wohl nicht mehr verwenden, oder? Wobei ich mich im Programm auch gut selbst drum kümmern kann dass nur eine Instanz erstellt wird.

    Aber das schwierigere Problem ist , dass ich eine globale Zugriffsmöglichkeit auf die Objekte brauche, die nur einmal erstellt werden (zumindest für die Klasse DataOutputChn).

    Kann ich dazu in OutputChnA eine statische Memberfunktion verwenden, die die Variable m_NextCall setzen kann, so dass diese Variable für alle Objekte der Klasse DataOutputChn immer gleich wäre (da ich ja von der Klasse sowieso nur ein Objekt erstellen werde)?
    Nachdem die Variable in SchedChannelA nicht "static" deklariert ist, müsste ich in der Klasse "OutputChnA" noch einmal eine "static m_NextCall"-Variable deklarieren und die alte Variable damit überschreiben.
    Aber gibt dann die Funktion SchedChannelA::getNextCall() auch den Wert der "richtigen" Variable zurück?

    Oder ist es der bessere Weg, in OutputChnA eine neue Variable "static m_myNextCall" festzulegen, und eine Memberfunktion "static setNextCall()" zu schreiben?
    Wenn ich in SchedChannelA dann die Methode getNextCall() als "virtual" deklariere, kann ich in OutputChnA die Methode überschreiben und anstelle von m_NextCall m_myNextCall zurückgeben.
    Die Lösung sollte funktionieren, kommt mir aber irgendwie unsauber vor.

    Weiß jemand eine geschickte Lösung für dieses Problem?



  • Hallo Radix,

    Du kannst in jeder Klasse (Basis oder abgeleitete Klasse) eine statische Farbrikmethode implementieren. Dann kannst Du für jede Klasse die Objekterzeugung und die Anzahl der Objekte steuern.

    Ich verpacke dazu die Objekte in std::tr1::shared_ptr (bzw. boost::shared_ptr).

    //Base.h:
    
    #include <memory> // oder "boost/Smart_Ptr.hpp", wenn kein tr1 vorhanden
    
    class Base;
    typedef std::tr1::shared_ptr<Base> BasePtr; // oder boost::shared_ptr<>
    
    class Base{
    
       private:
          virtual Base( /* Parameter für Base */ ); // keine direkte Objekterzeugung
    
       //...
       public:
          static BasePtr GetInstance( /* Parameter für Base */ ){
    
              // hier die Objekterzeugung für Base-Objekte
              // durchführen und steuern
          }
    };
    
    // Derived.h:
    #include "Base.h"
    
    class Derived;
    typedef std::tr1::shared_ptr<Derived> DerivedPtr;
    
    class Derived : public Base{
    
       private:
          virtual Derived( /* Parameter für Derived */ ); // keine direkte Objekterzeugung
    
       //...
       public:
          static DerivedPtr GetInstance( /* Parameter für Derived */ ){
    
              // hier die Objekterzeugung für Derived-Objekte
              // durchführen und steuern
          }
    };
    
    // main.h:
    
    #include "Base.h"
    #include "Derived.h"
    
    int main(){
    
       // ...
    
       BasePtr myBase = Base::GetInstance(/*Parameter*/);
       myBase->...;
    
       // ...
    
       DerivedPtr myDerived = Derived::GetInstance(/*Parameter*/);
       myDerived->...;
       return 0;
    };
    


  • Ich dachte jetzt, die Lösung wäre einfach, mich selbst um die einmalige Erstellung zu kümmern und einfach eine "static getInstance()"-Methode zu verwenden, allerdings gibt's ein ziemlich seltsames Verhalten.

    Um getInstance() ohne Parameter aufrufen zu können, aber die Erstellung des Objekts mit Parametern machen zu können, kümmere ich mich selbst darum dass es nur eine Instanz gibt.
    getInstance() dient also nur dem globalen Zugriff.

    Hier mal mein Code:

    // SchedChannelA.h
    class SchedChannelA
    {
    	public: 
    		SchedChannelA(); // setzt default werte fuer ein paar member var
    		string getChnName();
    		long long unsigned int getNextCallTime();
    	private:
    		long long unsigned int m_NextCall;
    		string m_ChnName;
    };
    
    // OutputChnA.h
    class OutputChnA : public SchedChannelA
    {
    	public:
    		void triggerCall();
    		OutputChnA(std::string filenameP, std::vector<MeasChnA*> MeasChnVectP); 
    		static OutputChnA* getInstance();
    	protected:
    		static OutputChnA* m_instance;
    
    }
    
    //OutputChnA.cpp
    
    OutputChnA* OutputChnA::m_instance = NULL;
    
    OutputChnA::OutputChnA(string filenameP, vector<MeasChnA*> MeasChnVectP):
        /* ... */
    {
        m_instance = this;
    }
    
    void OutputChnA::triggerCall()
    {
        m_NextCall = Clock::getTimestamp();
    }
    
    OutputChnA* OutputChnA::getInstance()
    {
        if( m_instance )
            return m_instance;
        else
            /* ... */
    }
    
    // derived classes: HtmlOutputChn mit ChnName = "HtmlOutput", DataOutputChn mit ChnName = "DataOutput", ...
    
    // main.cpp
    int main()
    {
    /* ... */
    
        OutputChnA* chnPtr = DataOutputChn::getInstance();
        chnPtr->triggerCall();
        cout << "ChnName: " << chnPtr->getChnName() << "\tSet trigger to: " << chnPtr->getNextCallTime() << endl;
    }
    

    In main() erhalte ich jetzt zwar für chnPtr->getNextCallTime() den gerade gesetzten Wert, aber für chnPtr->getChnName() den Wert "HtmlOutput". In meinem DataOutputChn-Objekt habe ich dagegen einen unveränderten NextCall-Wert stehen (die Werte des DataOutputChn kann ich sehen, weil der Channel auch in einem std::vector<SchedChnA*> liegt, dessen Inhalt ich mir komplett ausgeben lasse).

    Irgendwie ist bei mir also noch ganz schön der Wurm drin.



  • Zeig mal bitte den Code, wo Du ChnName für die OutputChnA-Objekte setzt.

    Außerdem: Denk daran, dass Dein Singleton-Code so nicht threadsafe ist!



  • Roger Wilco schrieb:

    Zeig mal bitte den Code, wo Du ChnName für die OutputChnA-Objekte setzt.

    Diese Variable wird immer im Konstruktor der konkret ausgeprägten Klassen gesetzt:

    HtmlOutputChn::HtmlOutputChn(string filenameP, vector<MeasChnA*> MeasChnVectP):OutputChnA(filenameP, MeasChnVectP)
    {
        m_ChnName = "HtmlOutput";
        m_CallIntv = 20*1000; // for testing purpose: 20 sec in msec
    }
    
    DataOutputChn::DataOutputChn(string filenameP, vector<MeasChnA*> MeasChnVectP):OutputChnA(filenameP, MeasChnVectP)
    {
        m_ChnName = "DataOutput";
    }
    
    // Konstruktor der abstrakten Basisklasse
    OutputChnA::OutputChnA(string filenameP, vector<MeasChnA*> MeasChnVectP):
        m_spFileWriter(new FileWriter(filenameP, eOverwrite)),m_MeasChnVect(MeasChnVectP)
    {
        m_isDisplayChn = false;
        m_instance = this;
    }
    

    Außerdem: Denk daran, dass Dein Singleton-Code so nicht threadsafe ist!

    Das ist okay, ich verwende keine Threads.



  • Aber m_ChnName ist doch ein privates Attribut der Basisklasse SchedChannelA. Dann kannst Du doch darauf gar nicht zugreifen.



  • Ahhh...'tschuldigung, ein Malheur beim Code-Kürzen. Die Attribute sind in der SchedChannelA-Klasse nicht private, sondern protected deklariert.



  • Ok, würde ich zwar als privates Attribut belassen und denn ChnName als Aufrufparameter des SchedChannelA-Konstruktors übergeben, aber das löst das Problem nicht.

    Bist Du mal mit dem Debugger den Code durchgegangen? So wüsste ich nicht, woran das liegt.



  • Roger Wilco schrieb:

    Ok, würde ich zwar als privates Attribut belassen und denn ChnName als Aufrufparameter des SchedChannelA-Konstruktors übergeben, aber das löst das Problem nicht.

    Da fehlt mir manchmal einfach die Erfahrung um das "richtig" zu machen. Ich dachte, protected-Vererbung wäre eleganter weil ich sonst in den abgeleiteten Klassen ggf. über den vollqualifizierten Namen auf die Variable zugreifen muss.

    Bist Du mal mit dem Debugger den Code durchgegangen? So wüsste ich nicht, woran das liegt.

    Bin ich, aber da hab ich auch nichts gesehen was mich weitergebracht hätte. Ich hab jetzt zwar nicht die ultimative Lösung, aber ein Workaround, indem ich die entspr. Funktionen von OutputChnA nach DataOutputChn verlegt habe. Schön ist's nicht, aber es geht - solange ich die Funktion nur in dieser Klasse brauche.


Anmelden zum Antworten