Allgemeine Frage zu OOP,... Ordnung muß sein



  • Dieses Problem hat mich auch schon mehrmals beschäftigt.

    In der Vergangenheit habe ich es oft so gelöst, dass jeder Spielobjekt-Repräsentant (in deinem Fall eine Karte) ein eigenes Sprite besitzt. Ich dachte, ich wäre damit flexibler und nahm auch naiverweise an, es würde sich besser auf die Performance auswirken. Diese dürfte jedoch recht irrelevant sein, da ein Schreibvorgang/temporäres Objekt im Vergleich zu Grafikrendering kaum Rechenzeit beansprucht. Wenn es wirklich kritisch wird, kann man immer noch profilen und experimentieren.

    Jetzt würde ich Grafik und Spieltechnik/Logik wohl eher trennen. Ein Ansatz wäre, eine Grafikverwaltungsklasse zu kreieren, der man die Aufgabe übertragen könnte, temporäre Sprites zu erstellen, um Spielelemente zu zeichnen. Die Spielelemente selber wären dann auf ein Minimum beschränkt, was sich auch gut auf den Speicherverbrauch auswirken würde. Ausserdem wären spiellogische Operationen wie Kartenmischeln etc. viel weniger schwerfällig, da wirklich nur die relevanten Daten umkopiert werden müssten.



  • Nexus schrieb:

    Ausserdem wären spiellogische Operationen wie Kartenmischeln etc. viel weniger schwerfällig, da wirklich nur die relevanten Daten umkopiert werden müssten.

    Das alleine klingt schon sehr sinnvoll !

    Btw Mischen: Deswegen gefällt mir python 😉

    meine_liste = (karte_1,....,karte_32)
    random.shuffle( meine_liste )
    

    OK, die funktion in C ist auch schnell geschrieben, aber kann sie auch gemischte Datentypen mischen (oder enthalten) ? 😃



  • Deswegen gefällt mir C++: 😉

    #include <algorithm>
    #include <Karte.hpp>
    
    int main()
    {
        std::vector<Karte> Deck;
        // hier Container befüllen
    
        std::random_shuffle(Deck.begin(), Deck.end());
        // Deck ist nun gemischelt
    }
    

    Für ausführlichere Informationen zur STL siehe www.cplusplus.com und die Artikel dieses Forums.



  • sdl-fan schrieb:

    Btw Mischen: Deswegen gefällt mir python 😉

    meine_liste = [b](karte_1,....,karte_32)[/b]
    random.shuffle( meine_liste )
    

    Nexus schrieb:

    Deswegen gefällt mir C++: 😉

    std::vector<Karte> Deck;
        // hier Container befüllen :arrow_right: Geht in C++ nicht so einfach wie in Python
    
        std::random_shuffle(Deck.begin(), Deck.end());
    


  • einen schrieb:

    // hier Container befüllen :arrow_right: Geht in C++ nicht so einfach wie in Python
    

    Ich meinte auch eher das random_shuffle() . Das Feature mit der Initialisierungsliste für Container wird übrigens im nächsten C++-Standard enthalten sein.

    Momentan muss man noch push_back() benutzen, was aber nicht schlimm ist. Gerade für dieses Beispiel eignet sich eine Schleife sowieso viel mehr als eine riesenlange Initialisierungsliste.

    Die Links zu den STL-Artikeln hier im Forum, deren Begutachtung sich auf alle Fälle lohnt:
    1) Container
    2) Iteratoren und Algorithmen
    3) Hilfsklassen und Erweiterungen



  • Sry, ich will den Thread nicht überreizen, aber was ich mit dem python-Vorteil meinte war:

    Erstelle oder Mische bitte in C/ C++ Listen, Vektoren, etc die verschiedene! datentypen gleichzeitig beinhalten 😉



  • sdl-fan-002 schrieb:

    Erstelle oder Mische bitte in C/ C++ Listen, Vektoren, etc die verschiedene! datentypen gleichzeitig beinhalten 😉

    Wozu braucht man das? Man muss dann trotzdem jeden Typ einzeln handhaben und Fälle unterscheiden (und: es ginge in C++ auch, z.B. mit Boost.Any). 🙂



  • Nexus schrieb:

    Wozu braucht man das?

    Naja, man erhält eine art dynamische struktur...

    Btw mag ich C++ lieber als Python, aber Python ist so irre einfach und man kann kleine Ideen in 1/3 der Zeit umsetzen...



  • sdl-fan-003 schrieb:

    Btw mag ich C++ lieber als Python, aber Python ist so irre einfach und man kann kleine Ideen in 1/3 der Zeit umsetzen...

    Ja, C++ ist halt bei vielen Dingen relativ mühsam und man hat teilweise verhältnismässig lange für kleine Dinge. Das ist aber wieder eine Frage der Übung...

    Aber zu deiner anderen Antwort: Das würde mich wirklich interessieren, nicht weil ich einen C++-vs.-Python-Flame anstrebe, sondern weil es mich tatsächlich wundert. Was meinst du mit "dynamischer Struktur"? Wenn einfach alle Typen vorkommen können, wie will man diese spezifisch ansprechen? Man muss ja irgendwo noch Typinformationen abspeichern, und da finde ich es eleganter, die Fälle gleich separat handzuhaben.



  • Nexus schrieb:

    Man muss ja irgendwo noch Typinformationen abspeichern, und da finde ich es eleganter, die Fälle gleich separat handzuhaben.

    Das ist das wunderbare an Python: Jede Variable kann einen beliebigen Datentypen beinhalten, ohne dafür spezifisch deklariert worden zu sein.
    Imo reicht es die Länge der Liste zu ermitteln und dann bis zum Ende durchzuiterieren...

    Btw Thx Nexus für (nicht nur hier) kreative und informative Antworten, dickes Lob.



  • sdl-fan-002 schrieb:

    Erstelle oder Mische bitte in C/ C++ Listen, Vektoren, etc die verschiedene! datentypen gleichzeitig beinhalten 😉

    das ist in c++ prinzipell auch möglich. nicht c++ ist das problem, sondern dessen 'dumb user base', die z.b. fragen stellt wie:

    Nexus schrieb:

    Wozu braucht man das?



  • c++fan.2008 schrieb:

    nicht c++ ist das problem, sondern dessen 'dumb user base', die z.b. fragen stellt wie:

    Nexus schrieb:

    Wozu braucht man das?

    Genau solch kritische Fragen, können manchmal das Bewußtsein anregen...



  • sdl-fan-004 schrieb:

    Das ist das wunderbare an Python: Jede Variable kann einen beliebigen Datentypen beinhalten, ohne dafür spezifisch deklariert worden zu sein.
    Imo reicht es die Länge der Liste zu ermitteln und dann bis zum Ende durchzuiterieren...

    Aber beim Durchiterieren ist es ja jedes Mal anders, was man mit dem aktuellen Element macht, oder? Wäre es von daher nicht einfacher, von Anfang an jeden Typ einzeln zu behandeln?

    Tut mir leid, ich kann es mir nicht ganz vorstellen, vielleicht bin ich als C++-Programmierer auch diesbezüglich etwas zu engstirnig. 😉

    sdl-fan-004 schrieb:

    Genau solch kritische Fragen, können manchmal das Bewußtsein anregen...

    Wie wahr. Aber abgesehen davon hat es sowieso keinen Wert, unregistrierten Benutzern, die nichts zum Thema beitragen und diskutierfreudige Leute als "dumb users" bezeichnen, auch nur irgendeine Beachtung zu schenken.

    sdl-fan-004 schrieb:

    Btw Thx Nexus für (nicht nur hier) kreative und informative Antworten, dickes Lob.

    Ich helfe gerne, und finde es nett, wenn Leute das auch schätzen. 🙂



  • Aber beim Durchiterieren ist es ja jedes Mal anders, was man mit dem aktuellen Element macht, oder? Wäre es von daher nicht einfacher, von Anfang an jeden Typ einzeln zu behandeln?

    Nicht unbedingt. Wenn sie die gleiche Schnittstelle haben (z.B Ausgabe), kannst du das ganz gut benutzen. C++ hat da ja ein ähnliches Konzept, nennt sich abstrakte Basisklasse. 😉 ( Jetzt sollten dir auch ein paar Anwendungsbeispiele einfallen. :))



  • Die Geschichte mit den verschiedenen Datentypen in einem Container ist eben einer der grundlegenden Unterschiede zwischen C++ und Python: statische vs. dynamische Typisierung. beides hat je nach Anwendungsgebiet seine Vor- und Nachteile und seine Daseinsberechtigung.

    Bei dynamischer Typisierung kann man wirklich alles in einen Container stopfen. Dafür muss man später bei der Verarbeitung damit rechnen, dass einzelne Elemente eine benötigte Operation nicht anbieten.
    Bei statischer Typisierung muss alles vom gleichen Typ sein (bzw. mit polymorphen Zeigern von verwandten Typen), dafür kann man davon ausgehen dass alle Elemente die gleichen Operationen anbieten.

    Zusammengefasst heißt das in dem Beispiel also: Flexibilität vs. Sicherheit



  • sdl-fan-004 schrieb:

    [Das ist das wunderbare an Python: Jede Variable kann einen beliebigen Datentypen beinhalten, ohne dafür spezifisch deklariert worden zu sein.
    Imo reicht es die Länge der Liste zu ermitteln und dann bis zum Ende durchzuiterieren...

    Das nennt man "Weak Typing". Bei C++ wurde bewusst auf "Strong Typing" gesetzt, weil es allgemein die Betriebssicherheit erhöht. Implzite Konvertierungen können z.B. ziemlich böse sein, wenn man nicht aufpasst und sich z.B. verschreibt.
    So ziemlich auf die Spitze wird das Strong Typing in Ada getrieben, wo man u.U. Integers nicht einander zuweisen kann.
    Beispiel:

    type Decibel_Type is new Float;    --Typ fuer Pegel in dB
    type Frequency_Type is new Float;  --Typ fuer Frequenzen in Hz
    
    Freq  : Frequency_Type := 0.0;     --Deklaration/Initialisierung
    Level : Decibel_Type   := 10.0;    --Deklaration/Initialisierung
    
    Freq := Level;  --Versuch einer Zuweisung, aber Frequenzen != Pegel -> Error
    Freq := Frequency_Type(Level); -- Typecast. Das geht, aber man muss dem Compiler extra sagen, dass man solchen Unsinn machen will
    

    Man kann also z.B., wenn man es auf die Spitze treibt, für verschiedene Maßeinheiten unterschiedliche Typen definieren. Im obigen Beispiel sind beide Typen zwar Float, aber eben trotzdem nicht gleich. Warum das, trotz des höheren Schreibaufwands ein Vorteil sein kann, sollte wohl klar sein, vor allem, wenn es sich um ein Interface handelt, dass von vielen Programmierern benutzt wird.
    Das Typsystem von C++ ist nicht ganz so streng, aber auf jeden Fall schon mal deutlich sicherer als irgendwelche Sprachen mit völlig loser Typisierung.



  • du sagst, das Typsystem in C++ sei nicht ganz so streng, das kann man so oder so sehen. Es ist nur nicht so einfach, einen neuen Typen zu erschaffen, der den gleichen Wertebereich hat wie ein anderer Typ. Dein Beispiel sähe in C++ etwa so aus:

    struct Decibel_Type
    {
      float value;
      explicit Decibel_Type(float f) : value(f) {}
      operator float() {return value; }
    };
    struct Frequency_Type
    {
      //analog
    };
    
    Frequency_Type Freq = 0.0f;
    Decibel_Type Level = 10.0f;
    
    Freq = Level;  //Versuch einer Zuweisung, aber Frequenzen != Pegel -> Error
    Freq = Frequency_Type(static_cast<float>(Level)); // Typecast. Das geht, aber man muss dem Compiler extra sagen, dass man solchen Unsinn machen will
    

    (den ganzen privtae/public-krams und andere Dinge mal außer acht gelassen)



  • pumuckl schrieb:

    [...]

    Jo, aber Dein Beispiel lässt problemlos sowas zu:

    float unsinn = Freq + Level;
    
    //oder sowas
        Frequency_Type Freq(0.0f);
        Decibel_Type Level(Freq);
    

    In Ada würde auch das nicht gehen. Mal ganz zu schweigen vom Aufwand den man für Zuweisungen treiben muss. Das würden dann zig Float-Wrapper werden, und was man von POD-Wrappern zu halten hat, sollte wohl klar sein.
    Beispiel:

    Decibel_Type level(0.0f);
        Decibel_Type inc(3.0f);
    
        level = level + inc; //geht nicht
    

    PS: Das erinnert mich etwas an den Nachbau von OO in C. Das sind Features, die hat die Sprache einfach nicht. Man kann es nachbauen, aber das ist sehr fehleranfällig und mühsam. Kosten/Nutzen stehen nicht im gesunden Verhältnis zueinander.



  • Mein Beispiel war nur mal eben hingerotzt. Es gibt durchaus Möglichkeiten, die Konvertierungsoperatoren wegzulassen bzw. zu ersetzen, und arithmetische Operatoren zu implementieren ist in dem Fall bestenfalls eine Anfängerübung. Ich habe auch nicht behauptet, dass all das besonders schön oder elegant aussähe, ich wollte lediglich darauf hinaus, dass derartige Wrapper als Typsicherheits-Schutz möglich sind, aber eben nicht so schnell zu erschaffen wie in Ada.

    Im Übrigen gabs grade für dein Beispiel (physikalische Werte) vor einiger Zeit ein Beispiel hier im Forum, wie man eine entsprechende Bibliothek aufbauen könnte:

    template <int length_dim, int time_dim, int weight_dim>
    class PhysicalUnit
    {
      double value; //in kg/m/s - System
      //...
    };
    
    //Diverse Operatoren...
    
    //typedefs für gebraeuchliche Groessen:
    typedef PhysicalUnit<1,0,0> Length;
    typedef PhysicalUnit<2,0,0> Area;
    typedef PhysicalUnit<1,0,-1> Velocity;
    typedef PhysicalUnit<1,0,-2> Acceleration;
    typedef PhysicalUnit<0,0,-1> Frequency;
    //...
    
    struct Units
    {
      const static Frequency Hz;      //Frequency(1)
      const static Length cm;         //Length(0.01)
      const static Acceleration grav; //Acceleration(9.81)
      const static Length inch;       //Length(0.0249)
    };
    
    int main()
    {
      Length breite = 5 * Units::cm;
      Length laenge = 10 * Units::inch;
      Area flaeche = breite * laenge;
    }
    

    Damit gehen dann solch sinnfreie Rechnungen auch nicht mehr.



  • Ich habe ja auch gar nicht gesagt, dass es überhaupt nicht geht. Es ist nur sehr mühsam und produziert zusätzlichen Code. In Ada werden einfach die Typnamen verglichen. Das ist schon einfacher.
    Du solltest vielleicht noch auf boost.units verweisen, wenn Du da schon abguckst. 😉


Anmelden zum Antworten