Funktion für abgeleitete Klasse als virtuell deklarieren?



  • Aber wenn man von einer konkreten Klasse nicht mehr ableiten soll, so ist dies hier auch nicht sinnvoll?

    class SceneNode
    {
    	std::list<SceneNode*> childrenList;
    public:
    	SceneNode();
    	virtual ~SceneNode();
    
    	virtual void Update();
    	void Release();
    
    	void addChild(SceneNode*);
    };
    
    class GUINode: public SceneNode
    {
    public:
    	GUINode();
    	~GUINode();
    
    	void Update();
    };
    

    Warum sollte das nicht sinnvoll sein? Somit muss man die Funktionen von SceneNode nicht in jeden Nodetyp implementieren sondern einfach ableiten und nur die Update() Funktion unterscheidet sich... So steht das in nem tutorial. Jeder Nodetyp hat seine childrenList, gleiche addChild() Methode und angepasste Updatefunktion.
    Würd gern wissen, wie man das besser machen könnte/sollte...

    MfG


  • Mod

    ceplusplus@loggedoff schrieb:

    Aber wenn man von einer konkreten Klasse nicht mehr ableiten soll, so ist dies hier auch nicht sinnvoll?

    class SceneNode
    {
    	std::list<SceneNode*> childrenList;
    public:
    	SceneNode();
    	virtual ~SceneNode();
    
    	virtual void Update();
    	void Release();
    
    	void addChild(SceneNode*);
    };
    
    class GUINode: public SceneNode
    {
    public:
    	GUINode();
    	~GUINode();
    
    	void Update();
    };
    

    Warum sollte das nicht sinnvoll sein? Somit muss man die Funktionen von SceneNode nicht in jeden Nodetyp implementieren sondern einfach ableiten und nur die Update() Funktion unterscheidet sich... So steht das in nem tutorial. Jeder Nodetyp hat seine childrenList, gleiche addChild() Methode und angepasste Updatefunktion.
    Würd gern wissen, wie man das besser machen könnte/sollte...

    MfG

    Dagegen ist nichts zu sagen, solange es keine Objekte gibt, die nur SceneNode-Objekte sind. Im Falle der String-Klassen etwa könnte man - ich bezweifle dass das die beste Methode ist, aber es ist nicht prinzipiell falsch - eine gemeinsame (öffentliche) Basisklasse benutzen, die das gemeinsame Interface beinhaltet einschließlich solcher Teile der Implementation, die in beiden Fällen gleich ist. Andererseits hab ich Strings noch nie wirklich polymorph benutzen müssen - dann bietet es sich an, gemeinsame Implementationsteile in einem Member oder einer privaten Basisklasse unterzubringen. Eine weitere Möglichkeit ist die Verwendung von Policies/Mixins - dabei wird im Grunde nur die Vererbungsreihenfolge (das ist folglich keine Anwendung von LSP) vertauscht. Nützlich ist Letzteres, weil es damit möglich ist, die besonderen Teile einer Implementation zusammenzufassen und z.B. Templates damit zu parametrisieren.



  • Hmm, also erhlich gesagt hatte ich nie ein wirklichers gutes Gefühl bei meiner String-Klassen-Vererbung. Einizger Unterschied zwischen den Klasse ist wie gesagt das "Speicher-Management" bzw. wird der Speicherverbrauch von SpecialString vor dem Löschen noch mehrfach überschrieben. Ob das sinnig ode unsinnig ist sei hier dahingestellt, es geht mir um die Vererbung. 😉

    Vielleicht wäre die Tatsche mit der gem. Schnittstellenklasse wirklich eine sinnvolle, vielleicht sogar die sinnvollste.

    Der ganze Grund warum SpecialString ünerhaupt von String ist die, dass es auch eine volständige Stringklasse ist - mit dem kleinen Unterschied - und ich 1. zu faul die komplette Implemenation 2-mal zu schreiben, was sicher auch nicht nötig ist, da es auch hier eine Möglichkeit gibt.

    P.S.: @camper: Was ist/heißt LSP? Was ist mit value-Objekten gemeint und warum sollten/dürfen hier keine Vererbungshierachien sein?

    Gruß cppler



  • cppler schrieb:

    P.S.: @camper: Was ist/heißt LSP? Was ist mit value-Objekten gemeint und warum sollten/dürfen hier keine Vererbungshierachien sein?

    Ein blindes Tippen vn
    http://de.wikipedia.org/wiki/LSP führt zu folgendem Artikel:
    http://de.wikipedia.org/wiki/Liskovsches_Substitutionsprinzip



  • cppler schrieb:

    Hmm, also erhlich gesagt hatte ich nie ein wirklichers gutes Gefühl bei meiner String-Klassen-Vererbung. Einizger Unterschied zwischen den Klasse ist wie gesagt das "Speicher-Management" bzw. wird der Speicherverbrauch von SpecialString vor dem Löschen noch mehrfach überschrieben. Ob das sinnig ode unsinnig ist sei hier dahingestellt, es geht mir um die Vererbung. 😉

    Vielleicht wäre die Tatsche mit der gem. Schnittstellenklasse wirklich eine sinnvolle, vielleicht sogar die sinnvollste.

    Der ganze Grund warum SpecialString ünerhaupt von String ist die, dass es auch eine volständige Stringklasse ist - mit dem kleinen Unterschied - und ich 1. zu faul die komplette Implemenation 2-mal zu schreiben, was sicher auch nicht nötig ist, da es auch hier eine Möglichkeit gibt.

    Gruß cppler

    Das klingt nach einem idealen Kandidaten für die Verwendung von policies. Beispielsweise so:

    template <class T, class MemoryPolicy>
    class String : private MemoryPolicy
    {
      /* die übliche Stringklasse, 
       * alles was mit Speicherverwaltung 
       * zu tun hat wird an die MemoryPolicy weitergeleitet 
       */
    };
    
    template <class T>
    struct DefaultMemPolicy 
    { /* "normale" Speicherverwaltung */ };
    
    template <class T>
    struct SpecialMemPolicy
    { /* was in specialstring gekommen wäre */ };
    
    //und dann z.B.
    typedef String<char, DefaultMemPolicy> DefaultString;
    typedef String<char, SpecialMemPolicy> SpecialString;
    
    //oder
    template <class T>
    struct StringTypes {
      typedef String<T, DefaultMemPolicy> DefaultString;
      typedef String<T, SpecialMemPolicy> SpecialString;
    };
    

    Und dann DefaultString oder StringTypes<T>::DefaultString nutzen usw.



  • Hmm, das Design sieht sehr interessant aus. Warum sind aber die MemoryPolicy Templates structs?

    Wenn die MemoryPolicy Templates das Speichermanagement betreiben, müssen diese doch dann auch (als public) die Membervariablen

    T      m_pData;
    size_t m_Size;
    

    enthalten, richtig?

    Dann hätte ich noch ein paar Fragen zur Vererbung.
    [1]: Die Klasse SpecialMemPolicy, würde ich gerne von DefaultMemPolicy erben lassen, denn SpecialMemPolicy-Objekt ist doch eigentlich ein DefaultMemPolicy-Objekt. Zudem würde ein wenig paar Implementationsdetails nur einmal schreiben müssen, wie das allozieren des Speichers etc...

    [2]: Wenn die String-Klasse privat von der MemPolicy-Objekten erbt, kann ich außerhalb der Klasse nicht mehr auf die Methoden wie z.B. Speicherfreigabe/Speicherallozierung zugreifen. Wenn ich mich recht erinnere heißt private Vererbung doch, dass nur die Implementation uns NICHT die Schnittstelle vererbt wird. Das heißt im folgenden Beispiel,

    class A
    {
    public:
    
      void Foo() { /* some code */ }
    };
    
    class B : private A
    {
    public:
    
      void Foo() { A::Foo(); }
    }
    

    dass B::Foo überhaupt nichts mit A::Foo zu tun hat und somit A::Foo nicht virtuell etc. sein muss, richtig? Somit brauchen private vererbte Objekte doch auch keinen virtuellen Destruktor.

    Gruß cppler



  • zu 0) Policies beinhalten meist garkeine Variablen, zumindest jedoch keine, die zur eigentlichen Klasse gehören. Andrei Alexandrescu hat in seinem Buch "modern C++ design" ein ganzes Kapitel über das Design von Policies geschrieben, und ein googlen führ schnell zu diesem Artikel (Link). Da deine Memory Policy soweit ich das verstanden hab nur dazu da ist, den Speicher für die Strings zu verwalten, und diese Strings soweit ich verstanden hab aus den T's bestehen sollen, muss die Policy nur wissen wie groß ein T ist und für wieviel T's sie Platz reservieren muss. Letzteres übergibt man der entsprechenden Funktion als Parameter, ersteres entweder auch als Parameter, oder man übergibts der Policy als Templateparameter, da sich das über die Lebenszeit der Policy ja nicht ändern wird vielleicht die bessere Wahl.

    zu 1) Du kannst natürlich die Policies voneinander erben lassen. Da aber unterschiedliche Policies ein unterschiedliches Verhalten haben, bleibt meist nichts anderes übrig als das Interface (oder ein Teil davon), das gleich ist. Da Policies außerdem nur einen kleinen Teil des Verhaltens der Stringklasse ausmachen, ist dieses Interface meist nur auf eine Handvoll Funktionen beschränkt. Am Ende bliebe also nur übrig, die Policy Klassen von einer sehr kleinen Klasse abzuleiten, von der sie nur das Interface erben, was pure virtual Funktionen impliziert. Da du aber Policies nie polymorph behandeln wirst und die Policies durch die virtuellen Funktionen nur langsamer (und größer) werden (Stichwort vtable), bringt es nichts, dort Vererbung einzubauen.

    zu 2) private Vererbung heißt "ist implementiert durch". Der Klient der String-Klasse braucht die Schnittstelle der Policy nicht zu kennen, da er ja nur auf die Strings selber, nicht auf ihre Speicherverwaltung zugreifen muss. Und Destruktoren müssen nur bei öffentlicher Vererbung virtuell sein, wenn Polymorphie beabsichtigt ist. (Wenn sie nicht beabsichtigt ist, sollte man sich Gedanken machen warum man dann öffentlich erbt). Man könnte statt privat zu erben, die Policy auch als private Member oder oft auch sogar als static member implementieren (letzteres nur, wenn sie intern keinen status speichern). Das würde die Bindung verringern (Member sind nicht so stark an die Klasse gebunden wie Basisklassen). Da man allerdings von vornherein nicht weiß, ob die einzelnen Policies intern einen Status speichern, fällt die static-member-Version weg, und da andererseits Policies doch oft nur aus einer Handvoll Methoden bestehen und keine eigenen Variablen haben, wählt man private Vererbung, um von der "Empty Base Class Optimization" zu profitieren (Leere Membervariablen belegen trotzdem Speicher, bei leeren Basisklassen darf der Compiler das wegoptimieren).

    Ich hab bei meinem Beispiel oben bei den typedefs natürlich Mist gebaut, dort müssen die Template-Parameter der policies angegeben werden:

    //und dann z.B.
    typedef String<char, DefaultMemPolicy<char> > DefaultString;
    typedef String<char, SpecialMemPolicy<char> > SpecialString;
    
    //oder
    template <class T>
    struct StringTypes {
      typedef String<T, DefaultMemPolicy<T> > DefaultString;
      typedef String<T, SpecialMemPolicy<T> > SpecialString;
    };
    

    Oder aber man verlangt, dass die Policies immer templates sind, dann sieht die Definition der Stringklasse etwas anders aus (mit der policy als template template parameter:

    template <class T, template <class> class MemoryPolicy>
    class String : private MemoryPolicy<T>
    

    und die typdefs bleiben so.

    Btw: die Allokatoren der STL sind so eine Policy für Speichermanagement wie hier beschrieben. In der STL-Dokumentation deiner Wahl wirst du bestimmt mehr dazu finden, und wenn ich net irre gibts im Magazin dieses Forums auch einen Artikel zum Thema.



  • So, nachdem ich mal etwas im Artikel gestöbert habe, - ich finde den Autor etwas schwierig. Andere lassen sich etwas besser lesen - habe ich mal das "Standard-Policy" wie folgt implementiert. Btw: Ich habe noch keinen Blick in die Stl Doku bzw. den Code gucken werfen können.

    // Default Memory Management
    template<class T>
    class DefaultMemoryPolicy
    {
    public:
    
        T *  Allocate(const size_t &Size)                     { return new T [Size]; }
        void Free    (T **pBuffer, const size_t &SizeInBytes) { delete [] *pBuffer; *pBuffer = 0; }
    };
    
    // Free enthält den Parameter SizeInByte, da das andere Policy diesen benötigt
    

    Die String-Klasse ist dann wie folgt implementiert:

    template<class T, class MemoryPolicyClass>
    class String : private MemoryPolicyClass
    {
    private:
    
        T      * m_pData;
        size_t   m_Size;
    
    public:
    
        String()
            : m_pData(0),
              m_Size(0)
        {}
    
        ~String()
        {
            Free();
        }
    
        // Erstellt einen neuen String
        T * New(const size_t &Size)
        {
            this->m_pData = MemoryPolicyClass::Allocate(Size);
            this->m_Size = Size;
            return this->m_pData;
        }
    
        void Free()
        {
            if (!IsEmpty())
            {
                MemoryPolicyClass::Free(&this->m_pData, this->m_Size * sizeof(T));
                this->m_Size = 0;
            }
        }
    };
    

    Ist das grober Unfug oder durchaus "echtes" C++?

    Gruß cppler



  • Kann man so nehmen. Ich würd allerdings die Signatur von Free() in der Policy etwas ändern:

    void Free   (T*& pBuffer, //<<- Referenz auf den Zeiger reicht
      const size_t //<-- hier keine Referenz
      /*SizeInBytes*/) //<<- in diesem Fall auskommentiert um compilerwarnungen zu verhindern
     { delete [] *pBuffer; *pBuffer = 0; }
    


  • pumuckl schrieb:

    Kann man so nehmen. Ich würd allerdings die Signatur von Free() in der Policy etwas ändern:

    void Free   (T*& pBuffer, //<<- Referenz auf den Zeiger reicht
      const size_t //<-- hier keine Referenz
      /*SizeInBytes*/) //<<- in diesem Fall auskommentiert um compilerwarnungen zu verhindern
     { delete [] *pBuffer; *pBuffer = 0; }
    

    Gut, die Referenz auf den Zeiger ist etwas schöner oder sogar besser? Gibt es da ein "man macht es so in C++" oder ist das "Geschmackssache"?

    Warum kommentierst du den Namen des Parameters SizeInBytes aus?

    P.S.: Die Dereferenzierung von pBuffer muss dann noch aufgehoben werden. 😉

    void Free   (T * & pBuffer, const size_t /*SizeInBytes*/)
     { delete [] pBuffer; pBuffer = 0; }
    

    Gruß cppler


Anmelden zum Antworten