Ist OOP in C++ eigentlich noch guter Stil?



  • Verschiedene Container für verschiedene Figurentypen? Super skalierbar. Sobald Du einen neuen Figurentyp hinzufügst, musst Du den neuen Container anlegen und bei allen Schleifen, die über die Container laufen, noch eine weitere für den neuen Container anlegen.

    Ja, Datenstrukturen in C haben aber zahlreiche Nachteile. Beispielsweise wird Kapselung nicht umgesetzt, das hat massive Nachteile bzgl. Erweiterbarkeit und Wartbarkeit Deines Codes. Und da das hier keine reinen Datenhaltungsstrukturen sind, sondern auch Methoden angewandt werden sollen, bietet sich eine struct hier nicht an.

    OOP macht auch in C++ für größere Projekte viel Sinn. Das ist eines der wichtigsten (das wichtigste?) Paradigma für sauberen und wartbaren Code. Templates können hier unterstützen, v.a. für generische Bibliotheksfunktionen oder eben Optimierungen zur Compilezeit.

    Du erwirtschaftest Dir hier durch den Einsatz keine Vorteile und schaffst Dir auf der anderen Seite Nachteile, wie oben aufgeführt. Für den von Dir beschriebenen Anwendungsfall würde ich also Templates nicht verwenden.



  • Eisflamme schrieb:

    Verschiedene Container für verschiedene Figurentypen? Super skalierbar. Sobald Du einen neuen Figurentyp hinzufügst, musst Du den neuen Container anlegen und bei allen Schleifen, die über die Container laufen, noch eine weitere für den neuen Container anlegen.

    Aber virtuelle Funktionen machen den Programmcode lahm.
    Wenn einem Geschwindigkeit egal ist, kann man auch Java nutzen.



  • Gomo schrieb:

    Eisflamme schrieb:

    Verschiedene Container für verschiedene Figurentypen? Super skalierbar. Sobald Du einen neuen Figurentyp hinzufügst, musst Du den neuen Container anlegen und bei allen Schleifen, die über die Container laufen, noch eine weitere für den neuen Container anlegen.

    Aber virtuelle Funktionen machen den Programmcode lahm.
    Wenn einem Geschwindigkeit egal ist, kann man auch Java nutzen.

    Aber manchmal leider unvermeidlich, wenn man dynamische Polymorphie braucht.



  • Nathan_logoff schrieb:

    Gomo schrieb:

    Eisflamme schrieb:

    Verschiedene Container für verschiedene Figurentypen? Super skalierbar. Sobald Du einen neuen Figurentyp hinzufügst, musst Du den neuen Container anlegen und bei allen Schleifen, die über die Container laufen, noch eine weitere für den neuen Container anlegen.

    Aber virtuelle Funktionen machen den Programmcode lahm.
    Wenn einem Geschwindigkeit egal ist, kann man auch Java nutzen.

    Aber manchmal leider unvermeidlich, wenn man dynamische Polymorphie braucht.

    Was ist statische Polymorphie?



  • Eisflamme schrieb:

    Verschiedene Container für verschiedene Figurentypen? Super skalierbar. Sobald Du einen neuen Figurentyp hinzufügst, musst Du den neuen Container anlegen und bei allen Schleifen, die über die Container laufen, noch eine weitere für den neuen Container anlegen.

    Kann man auch auf eine Stelle reduzieren.
    Indem man ein "mach das für alle Spielfiguren" Funktions-Template baut (Template-Parameter: der auszuführende Funktor).

    Ansonsten geht glaube ich "component based game engine" ein wenig in die Richtung.
    Wobei man dort nicht verschiedene Komponenten für die einzelnen Spielfiguren hätte, sondern für verschiedene "Teile" (bzw. Aspekte) die in den Spielelementen vorkommen.



  • out schrieb:

    Nathan_logoff schrieb:

    Gomo schrieb:

    Eisflamme schrieb:

    Verschiedene Container für verschiedene Figurentypen? Super skalierbar. Sobald Du einen neuen Figurentyp hinzufügst, musst Du den neuen Container anlegen und bei allen Schleifen, die über die Container laufen, noch eine weitere für den neuen Container anlegen.

    Aber virtuelle Funktionen machen den Programmcode lahm.
    Wenn einem Geschwindigkeit egal ist, kann man auch Java nutzen.

    Aber manchmal leider unvermeidlich, wenn man dynamische Polymorphie braucht.

    Was ist statische Polymorphie?

    In etwa der zweite Ansatz vom TE.



  • out schrieb:

    Was ist statische Polymorphie?

    Bloss nicht googeln, sonst findest du es am ende noch raus.
    http://en.wikipedia.org/wiki/Template_metaprogramming#Static_polymorphism


  • Mod

    out schrieb:

    Was ist statische Polymorphie?

    Na, das was der TE hier wohl vor hat. In C++ in der Regel mittels CRTP umgesetzt:
    template <class Derived>

    Wikipedia schrieb:

    template <typename Derived> struct base
    {
        void interface()
        {
             // ...
             static_cast<Derived*>(this)->implementation();
             // ...
        }
    };
     
    struct derived : base<derived>
    {
         void implementation();
    };
    


  • CRTP hätte allerdings den Nachteil, dass ich für alle Figuren eine Funktion implementieren muss und nicht auf die Standardfunktion zurückgreifen kann.

    Soweit ich weiß ich mit CRTP auch keine Polymorphie bei tieferer Vererbung mehr möglich.

    Beispiel:

    struct Spielfigur {
     virtual void bewege();
    };
    
    struct Buerger : Spielfigur {
     void bewege() override;
    };
    
    struct Reservist : Buerger {
     void bewege() override;
    };
    

    Wüsste ich nicht, wie ich das mit CRTP umsetzen sollte.


  • Mod

    Kolonist schrieb:

    CRTP hätte allerdings den Nachteil, dass ich für alle Figuren eine Funktion implementieren muss und nicht auf die Standardfunktion zurückgreifen kann.

    Soweit ich weiß ich mit CRTP auch keine Polymorphie bei tieferer Vererbung mehr möglich.

    Beispiel:

    struct Spielfigur {
     virtual void bewege();
    };
    
    struct Buerger : Spielfigur {
     void bewege() override;
    };
    
    struct Reservist : Buerger {
     void bewege() override;
    };
    

    Wüsste ich nicht, wie ich das mit CRTP umsetzen sollte.

    😕 Das ist doch quasi eine Auflistung der Fälle, für die der Trick überhaupt erfunden wurde.

    #include <iostream>
    
    using namespace std;
    
    template <typename Derived> struct base
    {
      void interface()
      {
        static_cast<Derived*>(this)->implementation();
      }
      void implementation()
      {
        cout << "Default\n";
      }
    };
    
    struct overrider : base<overrider>
    {
      void implementation()
      {
        cout << "Overridden\n";
      }
    };
    
    struct nonoverrider : base<nonoverrider>
    {
    };
    
    template <typename twicederived> struct derived : base<twicederived> 
    {
      void implementation()
      {
        cout << "Derived Base\n";
      }
    };
    
    struct twicederivednonoverrider : derived<twicederivednonoverrider>
    {
    };
    
    struct twicederivedoverrider : derived<twicederivedoverrider>
    {
      void implementation()
      {
        cout << "Derived overridden\n";
      }
    };
    
    int main()
    {
      overrider overrider;
      nonoverrider nonoverrider;
      twicederivednonoverrider twicederivednonoverrider;
      twicederivedoverrider twicederivedoverrider;
    
      overrider.interface();
      nonoverrider.interface();
      twicederivednonoverrider.interface();
      twicederivedoverrider.interface();
    }
    

    https://ideone.com/YDFXmp

    P.S.: Das bedeutet aber nicht, dass ich das in diesem Fall empfehlen würde. Ich habe nur festgestellt, dass es wohl das ist, was du im Eingangsbeitrag suchst. Die wirkliche Empfehlung ist aber

    Eisflamme schrieb:

    Verschiedene Container für verschiedene Figurentypen? Super skalierbar. Sobald Du einen neuen Figurentyp hinzufügst, musst Du den neuen Container anlegen und bei allen Schleifen, die über die Container laufen, noch eine weitere für den neuen Container anlegen.

    virtual function overhead < mehrere container overhead

    P.P.S: Obige Ungleichung hängt natürlich vom Anwendungsfall ab, aber erfahrungsgemäß braucht das Verhältnis Klassen:Instanzen nicht groß zu sein, damit die Aussage stimmt.


  • Mod

    Kolonists Problem besteht vermutlich darin, dass er mit
    Spielfigur -> Buerger -> Reservist
    beabsichtigt, sowohl Buerger als auch Reservist als nichtabstrakte konkrete Klassen einzusetzen. Und das ist dann mit CRTP etwas schwieriger. Andererseits würde so eine Benutzung sowieso das LSP verletzen...



  • Ich denke, man kommt um virtuelle Funktionen gar nicht herum, wenn man nicht irgendwelche anderen Tricks einsetzen will, die aber auch keine/kaum Geschwindigkeitsvorteile bringen.
    Man weiß ja beim Compilen noch gar nicht, welchen Einheitentyp man vor sich hat.
    Es kann ja sein, dass der Spieler einen normalen Bürger, einen Soldaten oder einen Panzer baut. Da das dynamisch ist, müsste man das mit switch-case abfragen und da hat doch wohl eine virtuelle Funktion klare Vorteile, sowohl von der Übersicht als auch möglicherweise von der Geschwindigkeit.


  • Mod

    Marthog schrieb:

    Ich denke, man kommt um virtuelle Funktionen gar nicht herum, wenn man nicht irgendwelche anderen Tricks einsetzen will, die aber auch keine/kaum Geschwindigkeitsvorteile bringen.
    Man weiß ja beim Compilen noch gar nicht, welchen Einheitentyp man vor sich hat.
    Es kann ja sein, dass der Spieler einen normalen Bürger, einen Soldaten oder einen Panzer baut. Da das dynamisch ist, müsste man das mit switch-case abfragen und da hat doch wohl eine virtuelle Funktion klare Vorteile, sowohl von der Übersicht als auch möglicherweise von der Geschwindigkeit.

    Er hat etwas vor in der Art von

    vector<Bürger> bürger;
    vector<Soldat> soldaten;
    // usw. für alle Einheitentypen
    
    // Etwas, das mit allen Einheiten gemacht werden soll:
    for (auto einheit: bürger) einheit.tu_was();
    for (auto einheit: soldat) einheit.tu_was();
    // usw. für alle Einheitentypen
    

    Wobei die genaue Auswahl der tu_was-Funktion von der Vererbungshierarchie abhängen soll, insbesondere mit der Möglichkeit, Implementierungen zu erben oder zu überschreiben.



  • for (auto einheit: bürger) einheit.tu_was();
    

    Ich hoffe, dass diese Zeile pseudocode-mäßig gemeint ist ...



  • sind umlaute in c++ nicht ein absolutes no-go?



  • LOL! schrieb:

    sind umlaute in c++ nicht ein absolutes no-go?

    Das ist nicht das Problem.
    Sondern die Schleife an sich, das wäre im Bereich typischer Anfänger Fehler.


  • Mod

    Meine Güte! Seid ihr wirklich alle nicht in der Lage, Pseudocode zu erkennen, wenn er euch ins Gesicht springt? Hätte ich

    Schleife über alle Bürger
    BEGIN
      tu was
    END
    

    schreiben müssen?



  • Wenn man fuer jede Einheit einen extra Container hat, dann wird es sehr umstaendlich, neue Einheiten hinzuzufuegen.



  • @SeppJ
    Wenn man was schreibt was gültiger C++ Code ist (oder fast, abgesehen von Trivialitäten), dann wäre net schlecht wenn er auch Sinn macht/funktioniert. Weil er dann vermutlich schnell als konkretes Beispiel verstanden wird.
    Und dann fehlt da nach dem auto ein & .
    Weil sonst immer nur eine Kopie des Bürgers & Soldaten modifiziert wird, was ja nicht Sinn der Sache ist.

    @Sone
    Statt nur aufzeigen wo was falsch ist/fehlt/... könntest du vielleicht auch gleich dazuschreiben was. Das wäre sinnvoll. Im Gegensatz zu nur rummoppeln dass irgendwas nicht passt, ohne zu sagen was. Was dann halt nicht so sinnvoll ist.


  • Mod

    hustbaer schrieb:

    @SeppJ
    Wenn man was schreibt was gültiger C++ Code ist (oder fast, abgesehen von Trivialitäten), dann wäre net schlecht wenn er auch Sinn macht/funktioniert. Weil er dann vermutlich schnell als konkretes Beispiel verstanden wird.
    Und dann fehlt da nach dem auto ein & .
    Weil sonst immer nur eine Kopie des Bürgers & Soldaten modifiziert wird, was ja nicht Sinn der Sache ist.

    Und das ändert inwiefern etwas am Sinn des Beispiels? Ist doch vollkommen egal, ob das Original oder eine Kopie angefasst wird, weil überhaupt nicht klar ist, was überhaupt gemacht wird. Eine Kopie kann genau so richtig sein, je nach Aufgabenstellung. Punkt ist: Es wird über alle Objekte iteriert. Das ist wichtig. Das wird mit möglichst wenig Syntaxballast vorgeführt.

    Wer da krampfhaft ein '&' vermisst, der weiß sowieso dass da eines hin sollte/könnte. Der soll sich dann bitte auch beschweren, dass der Code nicht im Blockscope ist, dass Umlaute nicht erlaubt sind, #include<vector> fehlt und keine einzige der Klassen und Funktionen deklariert ist. Wenn ihm dann noch nicht aufgefallen ist, was der Unterschied zwischen echtem Code und Vorführbeispielen ist, dann ... ach keine Ahnung. Dann ist er eben einfach zu dumm. Das sind diese Schnösel, wegen denen man hier keinerlei Antworten geben kann, ohne 80% des Textes damit zu verschwenden, alle Sonderfälle aufzuzählen, in denen eine gemachte Aussage (also die eigentliche Antwort auf eine Frage) vielleicht doch nur bedingt gelten könnte.


Anmelden zum Antworten