Ergebnis 1 bis 7 von 7

[C++]std::Array in Klassenkonstruktor über Schleife initialisieren

  1. #1 Zitieren
    Deus Avatar von Oparilames
    Registriert seit
    May 2004
    Ort
    ex contrariis
    Beiträge
    11.015
    cField.hpp
    Code:
    using sn = signed short;
    class cField//in ground – Mulde
    {
    	private:
    		const bool ownerIsPlayer1;
    		sn stonesIn;
    	public:
    		void add(const sn x=1);
    		void move();
    		sn getPoints(); 
    		std::string getUser(){if(ownerIsPlayer1)return "Player 1"; else return "Player 2";};
    		cField(const bool o=true);
    };
    cField.cpp
    Code:
    #include "cField.hpp"
    void cField::add(const sn x)
    {
        std::cout << "Addiere " << x << " zu " << stonesIn << std::endl;
        stonesIn+=x;
    }
    
    void cField::move()
    {
    }
    
    sn cField::getPoints()
    {
        return stonesIn;
    }
    cField::cField(const bool o):ownerIsPlayer1(o),stonesIn(0)
    {
        if(o)
            std::cout << "Spielbrettfeld erzeugt. Besitzer Player 1." << std::endl;
        else
            std::cout << "Spielbrettfeld erzeugt. Besitzer Player 2." << std::endl;
            add(3);
    }
    cBoard.hpp
    Code:
    #ifndef HPP_CBOARD
        #define HPP_CBOARD
    class cBoard
    {
        private:
    		std::array< cField, 12> fields; //referenz-Übergabe: std::array< cPan, 12 > const&
    	public:
    	    cBoard();
    };
    #endif
    cBoard.cpp
    Code:
    #include "cBoard.hpp"
    cBoard::cBoard():fields({true,true,true,true,true,true,false,false,false,false,false,false})
    {
        std::cout << std::endl;
        for(auto i = std::begin(fields); i != std::end(fields); ++i)
        std::cout << "Punkte: " << i->getPoints() << std::endl;
        std::cout << std::endl;
        std::cout << "Anzahl an Steinen: " << sizeof(fields)/sizeof(gamelogic::cField) << std::endl;
        fields[2].add(15);
        for(auto i = std::begin(fields); i != std::end(fields); ++i)
        std::cout << "Punkte: " << i->getPoints() << std::endl;
    [Bild: Code.png]

    Der obenstehende Code funktioniert zwar, doch wäre es mir lieber, ich
    könnte die Initialisierungsliste von fields per Schleife oder
    Algorithmus lösen. Hat jemand ein Verbesserungsvorschläge oder
    kann mich auf Fehler hinweisen?
    Ich hatte kurz versucht mit der std::fill- bzw. array.fill-Methode
    zu arbeiten, anstatt eine Schleife im Konstruktor zu setzen, aber ich kam
    damit weder klar, noch bin ich mir sicher, ob das effizienter wäre. Was
    gibt es dazu zu sagen?

    Mit freundlichem Gruß
    Oparilames
    Oparilames nachdem er seinen Gesellenbrief erhalten hat:
    »Das war's mit dir, du Mistvieh!«
    Oparilames ist offline

  2. #2 Zitieren
    Legende Avatar von jabu
    Registriert seit
    Jul 2011
    Beiträge
    7.363
    Ich hatte noch nicht so fürchterlich viel Zeit, um mich in deinen Code zu vertiefen, aber auf die Schnelle (Edit: und vllt. ein bisschen zu weit ausgeholt):

    Wegen der Deklaration als const in
    Code:
    class cField
    {
        private:
            const bool ownerIsPlayer1;
    ...
    kannst du ownerIsPlayer1 zwar bei der Initialisierung setzen (was deine Liste tut), später aber nicht mehr. Desweiteren bräuchtest du nur eine simple Zugriffsmethode (Memberfunktion) zum Setzen von ownerIsPlayer1. Edit: Sollte klar sein, dass das const dann weg muss und die Set-Funktion dann public sein muss.

    Dann kannst du im Konstruktor von cBoard über das Array fields iterieren (so ähnlich wie du das auch schon bei der Ausgabe gemacht hast) und die Werte über diese Zugriffsmethode entsprechend setzen.
    Dazu wirst du in diesem speziellen Fall zwei Schleifen benötigen oder, im Fall kleiner Veränderungen, nur eine, um die Werte aus der Default Initialization zu überschreiben (bei entsprechender Abänderung) oder denjenigen, welche du als 'true' vorgegeben hast (noch ist es so). Jedenfalls wäre Value Initialization blöd, wenn dein Array plötzlich größer würde, indem du dann händisch die weiteren Werte in die Initialisierungsliste beiflicken müsstest, was fehlerträchtig wäre, denn alle weiteren Werte wären hier implizit auf 0 bzw. false gesetzt (jetzt noch auf true), wenn du sie vergisst. Man könnte also für die Robustheit die natürlichen Defaults ausnutzen, um diese später zu überschreiben, anstatt künstlich erst anders zu initialisieren. Dann müsste false aber als Default erwünscht sein. Aber welche Variante du auch wählst, man sollte dem Code ansehen, was er tut. Immerhin sieht man aber, dass du alles auf true setzt. Aber es hat leider doch einen Haken:

    Kleines Update in diesem Zusammenhang:
    Code:
    cField(const bool o=true);
    ^Das ist unglücklich, da das vorgegebene true den Benutzer nicht dazu zwingt, die Initialisierungsliste zu komplettieren. Auf der Basis deines jetzigen Codes könntest du am Ende also Werte vergessen, sodass diese immer auf true gesetzt wären, ohne dass du es merkst. Entweder komplett Default Initialization, sodass man dieses Verhalten dem Code ansieht (was false ergäbe) oder aber die Vorbelegung mit o=true abschaffen, damit der Code nicht durch den Compiler geht, falls Initialisierungen vergessen wurden (z.B. indem das Array vergrößert wurde)! Edit: Oder du siehst dir mal den nächsten Beitrag an, wo nebenbei die Initialisierungsliste abgeschafft wird, weshalb es nichts zu vergessen gibt und das so bleiben kann.


    Ändern ließe sich das Verhalten durch weitere Kapselung von fields, wobei sich das Objekt selbst sinnvoll initialisieren würde. Ob das eine sinnvolle Herangehensweise ist, läge daran, was du insgesamt vor hast. Bei nur einer Instanz von fields wäre das vermutlich übertrieben.

    Es ist etwas blöd, wenn das Spielbrett irgendwie später mal skalieren soll und man alles von Hand beiflicken muss. Entweder erzeugt das Spielbrett ein Array entsprechenden Typs (also nochmal gekapselt und evtl. polymorph), welches selbstverwaltet und damit immer korrekt initialisiert ist, oder es erzeugt ein Array der benötigten Größe, sodass das Spielbrett weiß, wie es zu initialisieren ist. Dann musst du es natürlich nicht mehr kapseln. Beide Ansätze, einmal über das Typsystem, einmal über die Größe skalierend, wären grundverschieden, weshalb es darauf ankäme, was in der Zukunft noch erreicht werden soll.


    Entlang deines Ansatzes:

    Im Fall eines dynamischen Ansatzes könntest du gleich den Konstruktor von cBoard dergestalt abändern, dass du von vornherein angeben musst, welche Bereiche zu welchem Spieler gehören, damit die Definition von cBoard nicht mehr angefasst werden muss, nur weil das Spiel anders initialisert werden soll. Es sei denn, das wäre so in Stein gemeißelt, dann könntest du es so lassen. Dann wäre aber auch deine Initialisierungsliste besser als eine Schleife:

    Solche Initialisierungslisten sind gute Praxis, auch wenn sie doof aussehen. Dann weiß man wenigstens, dass hier eine ganz festgelegte Entscheidung getroffen wurde. Deinen angestrebten Schleifen sieht man das nämlich nicht mehr an. Es könnte also zu einer Veschlimmbesserung kommen, wenn eine dynamische Initialisierung nicht das ist, was dem Wesen des Spiels entspricht.

    Code sollte immer sprechend sein. Wenn das alles so statisch bleibt und die Spielerzuordnung sich nicht ändern soll, dann ist die Lösung, wie sie jetzt ist, auf jeden Fall besser. Wenn eine dynamische Lösung angestrebt ist, sollte sie auf allen Ebenen automatisch mitskalieren, sodass es nur noch einen zentralen Punkt zur Konfiguration gibt.

    Eventuell könnte es vorgespeicherte Konfigurationen geben, welche ausgelesen werden, um das Array zu initialisieren. Dann hättest du gleich ein konfigurierbares Setup und könntest alles andere, sofern mit den Daten skalierend, komplett agnostisch bzw. dumm (aber dafür robust) initialisieren. Je nach den Daten würde das Spiel anders funktionieren. In einer Übergangsphase könnten die Daten in einem konstanten Array liegen, wobei man auswählen kann, aus welchem Array man die Daten kopiert. Jedes Array wäre noch mal in eine Struktur verpackt, wo man eine ID auslesen könnte, um welches Setting es sich handelt. Ob das mit Kanonen auf Spatzen geschossen ist, kann ich von hier aus nicht beurteilen, mag sein.

    Edit/PS: Ansonsten weiß ich jetzt nicht, ob die Ausgabe vom Spielbrett veranlasst werden sollte. Die macht sich in dessen Konstruktor nicht gut, es ei denn, sie dient nur der Veranschaulichung. In den Konstruktor würde ich alles packen, was einen gültigen Zustand des Objektes gewährleistet und nichts darüber hinaus. Aber ich kenne dein Konzept nicht, daher nur allgemein gesprochen.

    Noch eine Idee: Bei der Instantiierung von cBoard einfach eine Referenz auf ein Konfigurationsobjekt übergeben, welches alle Informationen enthält, damit cBoard garantiert richtig initialisiert ist. Das bezöge sich natürlich nur auf konfigurierbare Settings.
    jabu ist offline Geändert von jabu (29.07.2015 um 22:49 Uhr) Grund: Update, s.o.

  3. #3 Zitieren
    Legende Avatar von jabu
    Registriert seit
    Jul 2011
    Beiträge
    7.363
    Zitat Zitat von Oparilames Beitrag anzeigen
    Der obenstehende Code funktioniert zwar, doch wäre es mir lieber, ich
    könnte die Initialisierungsliste von fields per Schleife oder
    Algorithmus lösen. Hat jemand ein Verbesserungsvorschläge oder
    kann mich auf Fehler hinweisen?
    Ich hatte kurz versucht mit der std::fill- bzw. array.fill-Methode
    zu arbeiten, anstatt eine Schleife im Konstruktor zu setzen, aber ich kam
    damit weder klar, noch bin ich mir sicher, ob das effizienter wäre. Was
    gibt es dazu zu sagen?
    Eine naive, aber bereits funktionsfähige Lösung wäre Folgendes:
    Code:
    cBoard::cBoard()  // optionale Initialisierung weggefallen
    {
        const size_t offset = fields.size() / 2;  // offset ist hier 6
    
        std::fill(std::begin(fields) + offset, std::end(fields), false);
    
        for(auto i = std::begin(fields); i != std::end(fields); ++i)
            i->show();
    ...
    }
    Dass das bisher nicht funktioniert, liegt nicht nur an dem const, sondern auch daran, dass dich deine Konsolenausgabe im Konstruktor von cField in die Irre führt. Denn es wird bei jeder einzelnen Zuweisung von true durch std::fill() an das Array ein impliziter Cast von bool nach cField vorgenommen, was nur dadurch möglich ist, dass jeweils ein temporäre Objekt vom Typ cField angelegt wird (hier insgesamt 6-mal), was jeweils einen zusätzlichen Konstruktoraufruf und damit eine weitere Ausgabe bedeutet.

    Der innere Zustand von fields ist jedoch korrekt, was man leicht nachvollziehen kann, wenn man die Konsolenausgabe z.B. in eine Methode "void cField::show()" auslagert.

    Die Intitialisiererliste kann unter Verwendung des von dir benutzten Default-Parameters natürlich leer ausfallen. Aber im Grunde könnte die Initialisierung des Arrays durch den Konstruktor von cBoard auch ganz wegfallen, weil es sich bei dem Array um ein Aggregat handelt, welches automatisch initialisiert wird. Wenn das Array initialisiert wird, wird der kompatible Standardkonstruktor aufgerufen, welchen du definiert hast, je cField genau einmal.

    Anstatt den Compiler jedes Mal ein temporäres Objekt erstellen zu lassen, können wir es genau einmal tun und eben dieses eine, korrekt initialisiert, zum Kopieren in das Array verwenden:
    Code:
    cBoard::cBoard()
    {
        const size_t offset = fields.size() / 2;  // offset ist hier 6
        
        const cField a(false);
    
        std::fill(std::begin(fields) + offset, std::end(fields), a);
    
        for(auto i = std::begin(fields); i != std::end(fields); ++i)
            i->show();
    ...
    Ohne std::fill() kommt es zu zwölf Konstruktoraufrufen, mit std::fill() und den sechs temporären Objekten, welche erst mit false initialisiert werden müssen, sind es also 12 + 6 = 18 Aufrufe. Mit dem einen 'cField a' sind es nur noch 12 + 1 = 13. Das sollte also besser sein. Es ist penibel darauf zu achten, dass die Konstruktoraufrufe nicht nebenbei Werte manipulieren, welche außerhalb des jeweiligen Objektes gültig sind und darüber zu einer Fehlinterpretation wegen weiterer, vorangegangener Konstruktoraufrufe führen können.

    Besser verwendest du noch das Keyword explicit, um zu unterbinden, dass implizit aus einem bool ein temporäres cField herbeigecastet wird:
    Code:
    class cField
    {
        private:
            explicit cField(bool o=true);
    ...
    }

    Falls das eine weitere cField, hier a genannt, stören sollte, müsstest du dir wohl eine Funktion zum Setzen des Wertes definieren. Dann genügt es also, jeweils nur diesen einen Wert zuzuweisen, was bei vielen Membervariablen oder z.B. in Loops, welche innerhalb eines Renderframes abgearbeitet sein müssen, einen spürbaren Performancevorteil bedeuten kann. Hier wirst du das aber bei den zwei Variablen nicht spüren können.
    jabu ist offline Geändert von jabu (30.07.2015 um 00:12 Uhr) Grund: Ergänzungen und Bereinigungen

  4. #4 Zitieren
    Deus Avatar von Oparilames
    Registriert seit
    May 2004
    Ort
    ex contrariis
    Beiträge
    11.015
    Hi Jabu!

    Erstmal besten dank und los geht es zu deinem ersten Post!
    Ich muss zugeben, ich habe am Code weiter gearbeitet, bevor ich deinen Code oder
    die folgenden Antworten gelesen habe.

    ownerIsPlayer1 ist deshalb const, weil sich das nach Initialisierung nicht
    mehr ändern kann. Nicht die Felder wechseln den Besitzer, sondern stonesIn.
    Eine mögliche Änderung, die du nicht angesprochen hast, weil sie für die
    Problemlösung irrelevant war, ist: Statt einem Zähler dieser Steine direkt
    einen std::vector auf Elemente der Klasse zu nutzen (cStones). Da cField jedoch
    keine Operationen mit den cStones durchführt, wird kein Zugriff benötigt.
    Jedenfalls fand ich deinen Satz bezüglich des Vergessens wichtiger Init-
    ialisierungswerte ausschlag gebend. Ich fand die Initialisierungsliste eben
    schön, weil ich mit ihr default-Werte setzen konnte und solche, die sich
    eh nie ändern. Wenn man dann also weiß, welche Werte sich vom default-Wert
    unterscheiden müssen, so müsste man nur diese wirklich beachten. Aber das
    ist Blödsinn. Also gehe ich lieber dazu über, dass alles händisch init-
    ialisiert wird.
    Den nächsten Beitrag gucke ich mir gleich an, aber erstmal weiter zu deinem.

    Ich kann diesen Satz leider beim besten Willen nicht verstehen.
    […]Entweder erzeugt das Spielbrett ein Array entsprechenden Typs
    (also nochmal gekapselt und evtl. polymorph), welches selbstverwaltet
    und damit immer korrekt initialisiert ist[…]
    Der Satz danach trifft den Nagel aber auf den Kopf. Die Größe des Spielbretts
    ist fest und verändert sich nicht mehr. Daher steht in der
    Klasse auf die Anzahl der Arrays, sonst hätte ich andere Container wie
    std::vector verwendet.

    Welches Feld welchem Spieler gehört ist sehr einfach und auch diese
    wechseln nie den Besitzer. Daher klingt Initialisierungsliste gut. Inzwischen
    bin ich auch von der Schleifen-Idee weg gekommen. Es ist halt immer so,
    dass die Hälfte des Spielbretts Spieler 1 und die andere Spieler 2 gehört.
    Das bleibt unabänderlich. Und daher ist der Aufbau auch fest.

    Die vorgespeicherten Konfigurationen habe ich vorgestern angefangen. Oder
    besser gesagt: Ich habe 3 Regel-Klassen erstellt, die vor dem Spielbrett
    erstellt werden. Regeln für das Brett, Regel für Spielzüge, Regel für
    Schlagregeln. Den Code dazu gibt es gleich auszugsweise, denn ich habe
    Fragen dazu.


    Und jetzt zu deinem zweiten Beitrag.
    Ja, das Kapitel über Konstruktoren ist bei mir noch überhaupt nicht fest
    verankert. Kopierkonstruktor, Defaultkonstruktor, benutzerdefinierter
    Konstruktoer … da gibt es gefühlt tausend Sorten.
    Explizit, ein von mir bisher gemiedenes Wort, weil mir unklar war, was es
    tut. Hier scheint es Sinn zu machen, deine Lösung habe ich inwzwischen für mich
    assimiliert.

    Nun, der Threadtitel kann angepasst werden. Ich denke ich werde hier
    weitere Fragen zu meinem Projekt stellen.

    Nun also ein wenig Code. Ich bitte um Rückmeldung. Auch, wie es mit der mauen
    Kommentierung aussieht.
    Code:
    namespace gamelogic
    {
        #include "logic/cField.cpp"
        #include "logic/cPlayer.cpp"
        #include "logic/cBoard.cpp"
        #include "logic/cRule.cpp"
        #include "logic/cRuleBook.cpp"
    };
    
    class cField
    {
        private:
    	    void setOwner(const bool o);
    		void move();
    	protected:
    		bool ownerIsNotPlayer1;
    		sn stonesIn = 6;  // Standardinitialisierung
    	public:
    	    bool getOwner();
    	    sn getContent();
    		void add(const sn x=1);
    		cField(const sn baseval,const bool o);
    		cField(){};
    };
    
    class cPreyField : cField
    {
    	public:
    		explicit cPreyField(const bool o);
    };
    
    class cPlayer
    {
    	private:
            const string name;
            bool active;
    		const cPreyField &winField;
       public:
           bool getActive();
           void changeActive();
           void chat(const string t);
           cPlayer(const string& n, const cPreyField& k);
           ~cPlayer();
    };
    
    
    class cBoard
    {
        private:
            const cPreyField& PreyField_0; // 0=Self/winFieldPlayer1, 1=Other/winFieldPlayer2
            const cPreyField& PreyField_1;
            //std::vector< cField > fields = {cField()};
            std::vector< std::unique_ptr<cField > > fields;
        public:
            cField &getField(int id);
            cBoard(const cPreyField& winFieldPlayer1, const cPreyField& winFieldPlayer2, const sn startFill, const sn fieldSize); // to fill, fieldsize
    };
    
    class cBoardRule
    {
        private:
            const bool ROR;                      // Has the side of a player one or two rows?
            const sn start_Pa;                    // Starting amount of Fields per player
    
            const sn start_PeK;                   // Starting amount of stones per PreyField per player
            const sn start_PePa;                  // Starting amount of stones per Field per player
        public:
            void sadd_PaP(bool const first ...); // Use exampe: for first 4 Fields of player 1 at the left side, and equal condition for player 2 sadd_PaP(1,1,1,1,0,0,  0,0,1,1,1,1);
            cBoardRule(const bool a, const sn b, const sn c, const sn d);
            const sn getFields();
            const sn getRows();
            const sn getFieldsPerPlayer();
            const sn getStartSmountOfstonesPerPreyField();
            const sn getStartAmountOfstonesPerField();
    };
    
    class cRuleDraw // Regeln für das Ziehen von Steinen
    {
        private:
            const sn chooseableField; // 0 = only own, 1 = only enemy, 2 = both
            const sn moveDirection; // 0 = anti clockwise, 1 = clockwise, 2 both allowed
            const sn amountstonesPerAction; // Schildkröten, die je Mulde abgelegt werden.
            const sn broodingPet;         // Schildkröte ist grüblerisch oder schwanger und
            const bool simplePreyField;     // PreyField wie normale Mulde während des Ziehens befüllen?
            const sn minstonesToDraw;       // Mindesanzahl Schildkröten, um Mulde als Auswahl zuzulassen
            const sn maxstonesToDraw;       // Maximalanzahl Schildkröten, um Mulde als Auswahl zuzulassen
        public:
            /// [Anmerkung: es folgen call by value getter…]
            cRuleDraw(const sn a, const bool b, const sn c, const sn d, const bool e, const sn f, const sn g);
    };
    
    
    class cRuleCatch
    {
        private:
            const sn lessThanToCatch;        // Sind wenider als diese Anzahl Schildkröten in ihrer Kuhle, können sie genommen werden.
            const sn mustNotBeMine;          // 0 = muss feindliche Mulde sein, 1 = muss eigene Mulde sein, 2 = kann beides sein
            const bool PreyFieldEnding;        // gibt es Gewinn, wenn der letzte Stein im PreyField eines Spielers landete?
    
            const sn onlyModuloTwo;          // 0 = ungerade(x%2==true) 1 = gerade(x/2==true) = egal
            const sn areaNextMinimumAmount;  // Im nächsten Feld müssen wenigstens soviele Schildkröten sein
            const sn areaNextMaximumAmount;  // Im nächsten Feld dürfen höchstens soviele Schildkröten sein
    
            const bool areaInFaceOfLootable;// Vom Feld gegenüber dürfen Tiere  entwendet werden. -
            const sn areaInFaceOfHas;        // … wenn mindestens soviele Tiere enthalten sind.
            const bool areInFaceOfEnemyToo; // … auch wenn es feindlich ist?
    
            const sn recursiveCatch;      // Können die Felder rekursiv ausgeräubert werden? (Felder dahinter == !moveDirection) 0 = nein, 1 = dahinter, 2 = davor
    
            const sn drawAgain;      // Wie oft danachnochmal gezogen und geschlagen werden kann
        public:
            /// [Anmerkung: es folgen call by value getter…]
            cRuleCatch(const sn aC, const sn bC, const bool cC, const sn cC2, const sn dC, const sn eC, const bool fC, const sn gC, const bool hC, const sn iC,const sn jC);
    };
    
    class cRuleBook//: cRuleDraw
    {
        private:
            const std::string name;
            cBoardRule BoardInit;
            cRuleDraw Drawing;
            cRuleCatch Catching;
        public:
    	    cRuleCatch& getCR(){return Catching;};
            cRuleDraw&  getDR(){return Drawing;};
            cBoardRule& getBR(){return BoardInit;};
            std::string getName();
            bool canChooseField(const cPlayer &pl, const cField &pa);
            int check();
            void doMove(const cPlayer &pl, const cField &pa);
            cRuleBook(const string na,
                     // cBoardRule parameter
                     const sn aR, // One row
                     const sn bR,        // Starting amount of Fields per player
                     const sn cR,        // Starting amount of stones per PreyField per player
                     const sn dR,        // Starting amount of stones per Field per player
    
                     // cRuleDraw parameter
                     const sn aD,
                     const bool bD,
                     const sn cD,
                     const sn dD,
                     const bool eD,
                     const sn fD,
                     const sn gD,
    
                     // RuleCatch parameters
                     const sn aC,
                     const sn bC,
                     const bool cC,
                     const sn cC2, //
                     const sn dC,
                     const sn eC,
    
                     const bool fC,
                     const sn gC,
                     const bool hC,
    
                     const bool iC,
    
                     const sn jC
    
                     //Catching
                     ):name(na),BoardInit(aR,bR,cR,dR), Drawing(aD,bD,cD,dD,eD, fD, gD), Catching(aC, bC, cC, cC2, dC, eC, fC, gC, hC, iC, jC)
                     {
                     }
            void sayRules();
    };
    
    
    
    
    
    cBoard::cBoard(const cPreyField& winFieldPlayer1, const cPreyField& winFieldPlayer2, const sn startFill, const sn fieldSize):PreyField_0(winFieldPlayer1),PreyField_1(winFieldPlayer2)
    {
        fields.reserve(startFill);
        const size_t offset = startFill / 2;  // offset ist hier 6
        for(size_t i=0; i<offset; i++)
            fields.push_back(unique_ptr<cField>(new cField(fieldSize,false)));
    
        for(int i=0; i<offset; ++i)
            fields.push_back(unique_ptr<cField>(new cField(fieldSize,true)));
    
    
        int j=0; int k;
        for(auto i = std::begin(fields),k=std::end(fields); i != k; ++i)
        {
            std::cout << ++j <<") Spieler " << i->get()->getOwner() << " mit " << i->get()->getContent() << " Punkten" << std::endl;
    
    
            /// Schau dir das mal bitte an, wenn ich dieses if auskommentiere, erhalte ich diese Meldung:
            //if(j==k-1);
            // error: no match for ‘operator==’ (operand types are ‘int’ and ‘__gnu_cxx::__normal_iterator<std::unique_ptr<gamelogic::cField>*, std::vector<std::unique_ptr<gamelogic::cField> > >’)|
        }
        j=0;
        ///  hier wird keine Fehlermeldung ausgespruckt. Warum?
        if(j==k-1);
    	
    	/// Warum kann ich fields nicht in einem oder 2 Rutschs belegen? Warum scheint das nur über einzelne push_back zu gehen?
        //std::fill(fields.begin(), fields.end()-offset, unique_ptr<cField>(new cField(s,0)));
        //std::fill(fields.begin(), fields.end()-offset, std::move(unique_ptr<cField>(new cField(s,0))));
        //std::generate(fields.begin()+offset, fields.end(), unique_ptr<cField>(new cField(s,1)));
    	
    
        fields[2].get()->add(15);
        std::cout << endl;
        for(auto i = std::begin(fields); i != std::end(fields); ++i)
            std::cout << ++j <<") Spieler " << i->get()->getOwner() << " mit " << i->get()->getContent() << " Punkten" << std::endl;
    }
    
    gamelogic::cField& cBoard::getField(int id)
    {
        return *fields.at(id).get();
    }
    
    
    
    cGameManager::cGameManager(const string& na,const string& nb, const int ac):currentRules({&x::RW_Hunters, &x::RW_Hunted, &x::RW_FireAndIce})
    /// Der Syntax ist eine Kurzgeschreibweise, siehe unabhängigen Codeschnippsel am Ende des Beitrags
    {
        activeRule->sayRules();
        board=std::make_shared<gamelogic::cBoard>(kSteffi,kOther,activeRule->getBR().getFieldsPerPlayer(),activeRule->getBR().getStartAmountOfstonesPerField());
        gamelogic::cField temp(false,0);
        initPlayers(na,nb);
    }
    
    cBoardRule::cBoardRule(const bool a, const sn b, const sn c, const sn d):
        ROR(a),
        start_Pa(b),
        start_PeK(c),
        start_PePa(d)
    {
        //
    }
    
    const sn cBoardRule::getRows(){return ROR;};
    
    /// …
    Falls es dir nicht sofort ersichtlich ist, wie das mit den Regeln funktioniert:
    Es gibt drei Phasenklassen für die Regeln, die alle Regeln des Spiels umfassen (sollen).
    Jeweils eine Variable dieser Phasen wird in cRuleBook gespeichert.
    Und für jede Variante des Spiels (Beispiel Schach: ohne Springer,
    Türme haben 2 Leben oder sowas) gibt es ein eigenes Regelwerk.
    So mache ich mir das Klassenkonzept aber irgendwie nicht richtig zu nutze.
    Ich überlege, ob ich das mit den Regeln nicht ganz anders angehen soll:
    Jedes Mitglied der Klasse cRule hat eine Konstante, die anzeigt,
    in welcher Spielphase sie zur Geltung kommt. Die Klasse hat jeweils
    eine do- und eine condition-Funktion. Diese werden beim
    Initialisieren übergeben. Beispiel (nicht kompilierbar, da ich damit nicht
    weiter komme, mir qualmt der Kopf)
    Code:
    #include <iostream>
    #include <vector>
    #include <functional>
    #include <algorithm>
    #define sn signed short
    enum class cRuleCondSort{exact, more, moreorequal, less, lessorequal};
    
    
    /*
    struct funktionszeiger{
        //public:
        //funktionszeiger operator(){return this;};
            int operator(int a)const{return a;};
            bool operator[]const{};
            sn operator<>const{};
    };
    */
    struct Funktionszeiger {
    int w,h;
    int area() const { return w*h; }
    };
    
    
    struct funktionszeiger { // definiert einen Funktor
    bool operator()(const int&a, const int&b) const {
    return a <= b;
    }
    };
    
    class cRuleCond
    {
    	private:
    		signed int operatorContact; // 0 = && 1 == || 2 == !
    		std::vector<cRuleCond*> inContactWith;
    		signed int haveToBe;
    		sn& argVal;
    		cRuleCondSort sic;
    	public:
    		void addContactCond(cRuleCond& i, signed int o)
    		{
    			operatorContact=o;
    			inContactWith.push_back(&i);
    
    			i.operatorContact=o;
    			i.inContactWith.push_back(this);
    		};
    		bool check(const sn targetVal)
    		{
    		    bool tmp;
                switch (sic)
                {
                    case cRuleCondSort::exact:
                        tmp= (targetVal == haveToBe); return tmp;
                    case cRuleCondSort::more:
                        tmp=(targetVal > haveToBe); return tmp;
                    case cRuleCondSort::moreorequal:
                        tmp=(targetVal >= haveToBe); return tmp;
                    case cRuleCondSort::less:
                        tmp=(targetVal < haveToBe); return tmp;
                    case cRuleCondSort::lessorequal:
                        tmp=(targetVal <= haveToBe); return tmp;
                    default:return false;
                };
    
    		    for(auto i=std::begin(inContactWith); i!=std::end(inContactWith); i++)
    		    {
    		        tmp=this->check(targetVal);
    		    }
    		    /*
    		    for_each(inContactWith.cbegin(), inContactWith.cend(),
                [targetVal,tmp,this]() mutable { tmp=this->check(targetVal);});
                */
    		}
    		cRuleCond(const sn h, const cRuleCondSort s, sn &tv):haveToBe(h),sic(s),argVal(tv){};
    };
    
    class cRule
    {
    	private:
    		std::vector<cRuleCond*> conditions;
    		void* pointerToTarget;
    		funktionszeiger doFunc;
        public:
    		void addCond(cRuleCond& c) {conditions.push_back(&c);}
    		void tryDo()
    		{
    		    bool tryIt;/*
    		    for(auto i=std::begin(cRuleCond); i!=std::end(cRuleCond); i++)
    		    {
    		        tryIt=i->check();
    		    }*/
    		    if (tryIt)
    		    {
    		        std::cout << "Es klappt!" << std::endl;
    		        doFunc(conditions[0]->check(2), conditions[1]->check(5));
    		    }
    		    else
    		        std::cout << "Regelverstoß!" << std::endl;
    		}
    /*	cRule(void* pt, void* pf):pointerToTarget(pt),doFunc(pf){std::cout << "CTor a"<<std::endl;};*/
    	cRule(){std::cout << "CTor b"<<std::endl;};
    
    };
    Funktionszeiger afaf;
    Funktionszeiger ffa;
    
    
    int main()
    {
        sn stones_in_A = 10;
        sn stones_in_B = 15;
        bool playerAAtTurn=false;
        cRule canDraw();
        cRuleCond cond_MoreThanFifeStonesIn(10,cRuleCondSort::moreorequal,stones_in_A);
        cRuleCond cond_StonesBelongsPlayerB((sn)false,cRuleCondSort::exact,(sn&)playerAAtTurn);
    
        cRule DrawNormal();
        DrawNormal.addCond(cond_MoreThanFifeStonesIn);
        DrawNormal.tryToDo();
    }
    Der Codeschnippsel:
    Code:
        namespace x{gamelogic::cRuleBook RW_Hunters("Jägerspiel",
                                         false, 6, 0, 6,
                                         0, 0, 1, 0, false, 0,0,
                                         0,2,false,1,2,6,false,0,false,1,0);}
    Edit:--> Zur Initialisierung von fields habe ich gerade was über Ver-
    schiebungssemantik gelesen und frage ich gerade, welcher der beiden Um-
    setzungen besser ist, oder ob da kein Unterschied besteht. Hier die beiden
    neuen Lösungsversuche:
    Code:
            fields.emplace_back(std::unique_ptr<cPan>(std::move(new cPan(fieldSize,true))));
    Code:
            fields.emplace_back(std::move(std::unique_ptr<cPan>(new cPan(fieldSize,true))));
    Spielt es überhaupt eine Rolle wo/ob ich move einsetze? Sorgt emplace_back
    nicht schon dafür, dass da alles glatt läuft?
    Oparilames nachdem er seinen Gesellenbrief erhalten hat:
    »Das war's mit dir, du Mistvieh!«
    Oparilames ist offline Geändert von Oparilames (01.08.2015 um 20:33 Uhr) Grund: Erweitert

  5. #5 Zitieren
    Legende Avatar von jabu
    Registriert seit
    Jul 2011
    Beiträge
    7.363
    Hi, Oparilames!

    Jepp, ich hatte in der Tat vermutet, dass std::array wohlbegründet gewählt wurde. Jedenfalls drückt dein Code das aus. Danke auch für die anderen klärenden Infos.

    Ich kann diesen Satz leider beim besten Willen nicht verstehen.
    […]Entweder erzeugt das Spielbrett ein Array entsprechenden Typs
    (also nochmal gekapselt und evtl. polymorph), welches selbstverwaltet
    und damit immer korrekt initialisiert ist[…]
    Das ist einer Idee zu einer Architektur entsprungen, wie man ein Konfigurationssystem durch Konfigurationsklassen, eine je Spielvariante, jeweils zu deren Selbstkonfiguration, hätte aufziehen können. Diese würden alle von einer Basisklasse abgeleitet. Die Ablaufsteuerung wäre dazu orthogonal. Ob das bei dir passen würde, kann ich jedoch nicht beurteilen, weshalb das nur als Anregung für eigene Ideen gemeint war.

    Die vorgespeicherten Konfigurationen habe ich vorgestern angefangen. Oder
    besser gesagt: Ich habe 3 Regel-Klassen erstellt, die vor dem Spielbrett
    erstellt werden. Regeln für das Brett, Regel für Spielzüge, Regel für
    Schlagregeln. Den Code dazu gibt es gleich auszugsweise, denn ich habe
    Fragen dazu.
    Klingt interessant, danke dafür. Ich freue mich über jegliche nützliche Informationen. Fragen zu Sprachelementen von C++ lassen sich jedoch leichter behandeln, wenn das jeweilige Problem in verallgemeinerter Form dargestellt und soweit isoliert ist, dass man den Kontext nicht mehr nachrecherhieren muss. Das würde auch die Chance erhöhen, Antworten von weiteren Leuten zu erhalten, wobei das die Möglichkeit zur gegenseitigen Korrektur und Ergänzung eröffnen würde.

    Nun also ein wenig Code. Ich bitte um Rückmeldung. Auch, wie es mit der mauen
    Kommentierung aussieht.
    Kommentierung könnte ausführlicher sein. Aber noch mehr würde es bringen, wenn man einen besseren Überblick hätte, auch über die Organisation des Projektes, was für mich einige Fragen aufwirft.

    Hier fangen sie an:
    namespace gamelogic
    {
    #include "logic/cField.cpp"
    #include "logic/cPlayer.cpp"
    #include "logic/cBoard.cpp"
    #include "logic/cRule.cpp"
    #include "logic/cRuleBook.cpp"
    };
    Umgekehrtes Inkludieren der Implementierung anstelle der Deklarationen bzw. Schnittstellen. Das kann zu Sackgassen im Projektsetup führen, sodass es irgendwann schwer werden könnte, etwas hinzuzufügen. Warum nicht der klassische Ansatz mit einem cpp-File und einem Headerfile je Modul? Wichtige Gründe, die dagegen sprechen (könnte immerhin sein)?

    Wie willst du die darauf folgenden Schnittstellen innerhalb anderer Module bekannt machen?
    Wirst du außerhalb dieses Moduls immer mit Vorwärtsdeklarationen auskommen?
    Möchtest du alles immer gemeinsam durch den Compiler jagen?
    Was ist mit Fehlern durch Mehrfachdefinitionen (spätestens der Linker würde sich melden)?


    Anderes Thema, const bei Wertübergabe an Funktionen:

    Es ist nicht falsch, aber aus der Perspektive der Nutzung dieser Schnittstellen ist es beim Lesen etwas viel Noise, denn man bleibt leicht daran hängen, indem man nach der Referenz oder nach dem Pointer sucht, weshalb das const jetzt nötig wäre, obwohl es nach außen hin irrelevant ist. Denn bei Call by Value handelt es sich bereits um eine Kopie, weshalb es keinerlei weiterer Spezifizierung einer Nicht-Modifizierbarkeit bedarf. Was die Funktion mit ihrer Kopie anstellt, ist ihre Angelegenheit.
    Sicherer wird die Verwendung der Schnittstelle dadurch auch nicht, ganz im Gegensatz zu const bei Pointern oder Referenzen.
    Wenn es um Sicherheit innerhalb der Funktion geht, lässt sich eine Konstante mit dem übergebenen Wert initialisieren. Der Compiler kann die zusätzliche Variable wegoptimieren.
    Wie so oft, sind solche Empfehlungen nicht als in Stein gemeißelt anzusehen, denn einen Königsweg gibt es nicht. Die Prioritäten können sich leicht verschieben. Daher hat man anfangs die Qual der Wahl, auf welchen Stil man sich festlegen möchte. Im Rahmen der Projekte, die man angeht, setzt sich dann die eine oder andere Variante durch, die man dann innerhalb des Projektes konsequent beibehalten sollte. Anm.: Nein, üblich ist das const an dieser Stelle nicht.


    Fragen, welche du dir zu Klassen stellen solltest, hier exemplarisch zu cPreyField:

    Was ist überhaupt ein Spielfeld?
    Was unterscheidet ein cPreyField von einem cField?
    Handelt es sich dabei um unterschiedliche Entitäten oder um unterschiedliche Eigenschaften derselben Entitäten?
    Werden Objekte des Typs cPreyField neben denen des Typs cField koexistieren?
    Ist cPreyField eine erweiternde Spezialisierung von cField?
    Wenn es sich um unterschiedliche Entitäten handelt, warum ist die Basisklasse nicht abstrakt?
    Wenn es sich nicht um unterschiedliche Entitäten, sondern Eigenschaften, handelt, warum sind die nicht nicht als Properties derselben Klasse angelegt?

    Ein eventueller Versuch, unterschiedliche Entitäten, also einerseits des Typs cField und andererseits des Typs cPreyField, ineinander überführen zu wollen, würde das Typsystem ad absurdum führen und die Sache nur unnötig verkomplizieren, wenn man den Gedanken zu Ende führt.

    Also gehe ich von der plausibelsten aller Varianten aus, dass ein cPreyField eine Spezialisierung von cField ist, welche sich unterschiedlich verhalten soll. Dann wäre jedoch, gemäß obiger Überlegungen, fix angelegt, welchen Typs die Felder jeweils sind, entweder ein unbesetzbares oder ein besetzbares Feld(?), solange das Spiel läuft. Soll es genau so funktionieren?
    Falls nicht, würde ich das noch mal überdenken. Falls zutreffend, kannst du es so machen. Dabei wäre zu bedenken, ob ein cPreyField wirklich nur ein besonderes cField ist. Wenn nicht, würde ich beide von einer abstrakten Basisklase erben lassen. Falls doch zutreffend, würde ich mir Gedanken machen, ob die Abstraktion stimmt, dass alle cField, ungeachtet der Tatsache, ob es sich dabei um ein cPreyField handeln könnte, grundsätzlich gleich behandelt werden sollen, indem es eben nur bei einem cPreyField ein besonderes Verhalten gibt. Falls das so ist, stehen die Chancen nicht schlecht, dass die Abstraktion passt.


    Einige weitere Anmerkungen zu const:
    Code:
    class cBoardRule
    {
        private:
            const bool ROR;      // falls nur einmal zu besetzen: gut und richtig (Edit: Nur bei der Initialisierung!)
            //...
        public:
            const sn getRows();  // dieses const ist Noise
            //...
    };
    Erst in der Umgebung des Aufrufers wird der Wert der Funktion, ein rvalue, der Variablen mysn (s.u.) zugewiesen, wobei einen rvalue charakterisiert, dass man nicht seine Adresse nehmen kann, ihn also nicht im Speicher findet, was soviel bedeutet, dass er solange nicht existiert, wie er nicht einem lvalue zugewiesen wird (oder nicht tatsächlich ein temporäres Objekt angelegt wird).
    Den rvalue selbst kann man aber nicht modifizieren, weshalb der Spezifizierer const hier überflüssig ist und eher in die Irre führt, als ob es sich um eine Referenz handeln würde, wo das const einen Sinn ergäbe. Zudem handelt es sich bei der Zuweisung bloß um eine Kopie, weshalb eine Modifizierung auf der Ebene des Aufrufers ausgeschlossen ist. Falls er sein Exemplar des Typs sn nicht modifizieren soll, verwendest du dort ein const (das andere const wäre wirkungslos):
    Code:
    const sn mysn = cBoardRule.getRows();
    Anm.: Es dürfte auch einleuchten, dass die Ressource mysn dem Aufrufer gehört, welcher dafür Speicher bereitstellt, nicht der aufgerufenen Funktion. Bei einem Pointer auf eine Ressource oder einer Referenz an eine solche, bezieht sich das const jedoch auf diese, nicht auf den Verweis an sich. Deswegen macht man auch nicht den Pointer const, was wiederum wirkungslos wäre, sondern den Wert, auf den er verweist. Denn der übergebene Pointer selbst ist eine Ressource des Aufrufers, nicht aber das Ziel, auf welches er verweist. Ebenso ist nicht der Character einer Referenz als const festzulegen, denn ihre Bindung ist sowieso nicht modifizierbar, sondern ihr Wert, für den sie ein Aliasname ist. Der Alias wird jedoch wie ein konstanter Pointer gebraucht, der automatisch mit der Adresse eines Objektes initialisiert wird und umgekehrt, ebenso automatisch dereferenziert wird, sodass es sich bei der Referenz weiterhin um dasselbe Objekt handelt, also keine Kopie angefertigt wird. Daher ist der Wert einer Funktion des Typs einer Referenz auf ein Objekt kein rvalue, sondern ein lvalue, den man vielleicht vor Manipulation schützen möchte, da er ein Objekt im Speicher repräsentiert. Dieser lvalue lässt sich also als const deklarieren, was man als einen Cast betrachten kann, der den Zugriff einschränkt. Einschränkende Zugriffe sind meistens implizit erlaubt, erweiternde eher nicht. Ich bleibe erst mal bei dieser traditionellen Unterscheidung zwischen rvalue und lvalue, obwohl man inzwischen weitere Spezifizierungen eingeführt hat.

    Edit: Ein anderes const ist aber angebracht, um anzuzeigen, dass diese Memberfunktion den Zustand des Objektes nicht verändern wird, was auch Manipulationen darüber unterbindet:
    Code:
    class cBoardRule
    {
        private:
            const bool ROR;
            //...
        public:
            sn getRows() const;  // dieses const ist sinnvoll
            //...
    };

    Code:
    int j=0; int k;
    for(auto i = std::begin(fields),k=std::end(fields); i != k; ++i)
    {
        // ...
        // Schau dir das mal bitte an, wenn ich dieses if auskommentiere, erhalte ich diese Meldung:
        // if(j==k-1);
        // error: no match for ‘operator==’
        // (operand types are ‘int’ and
        // ‘__gnu_cxx::__normal_iterator<std::unique_ptr<gamelogic::cField>*,
        //    std::vector<std::unique_ptr<gamelogic::cField> > >’)|
    }
    
    //  hier wird keine Fehlermeldung ausgespruckt. Warum?
    j=0;
    if(j==k-1);
    Dieses ist leicht zu erklären, denn j ist vom Typ int, und ein int lässt sich nicht mit einem Iterator (der ein verkappter Pointer ist), vergleichen.
    Die Typsicherheit hat dich also vor einem schwerwiegenden Bug bewahrt, denn du hast vermutlich vergessen, den Iterator zu dereferenzieren bzw. den Wert zu ermitteln, auf den er verweist.
    Du müsstest also einen Wert hinter (k-1) mit j vergleichen, nicht (k-1) selbst.
    Innerhalb des Scopes dieser Schleife ist k der Iterator, den du zugewiesen hast, nicht das k aus dem umschließenden Scope, welches als Name verdeckt wird und daher nicht zugreifbar ist.
    Du willst also vermutlich über einen Pointer auf eine Membervariable zugreifen, was du mit dem Pfeiloperator schon öfters gemacht hast.
    Aber Vorsicht: (k-1)->irgendwas ist eine potentiell gefährliche Aktion, weil du damit einen illegalen Zugriff unterhalb des ersten Elementes tätigen könntest, wenn der Container leer ist.
    Dass das zweite Beispiel keine Fehlermeldung ergibt, dürfte klar sein, da es sich um das k aus dem umschließenden Scope handelt, sodass int-Werte miteinander verglichen werden (wobei dieses int k uninitialisiert ist und der Compiler bei entsprechenden Einstellungen eine Warnung ausspucken sollte; falls nicht, unbedingt Warnstufe erhöhen).

    Das erst mal bis hierhin. Der Rest mit der Move-Semantik folgt später.
    jabu ist offline Geändert von jabu (03.08.2015 um 19:13 Uhr) Grund: inhaltliche Ergänzungen

  6. #6 Zitieren
    Deus Avatar von Oparilames
    Registriert seit
    May 2004
    Ort
    ex contrariis
    Beiträge
    11.015
    Zitat Zitat von jabu Beitrag anzeigen
    […]Hier fangen sie an:[…]
    Ich arbeite ungerne mit Projekt-Setups.
    Das Prinzip der Einbindung ist ganz einfach demonstriert:


    cBool.cpp
    Code:
    #include "cBool.hpp"
    cBool::cBool(bool a):
            value{a}
        {
        };

    cBool.hpp
    Code:
    #ifndef CBOOL_HPP
        #define CBOOL_HPP
        class cBool
        {
            private:
                bool value;
            public:
                cBool(const bool a);
        };
    #endif

    Projekt.cpp
    Code:
    #include "cBool.cpp"
    int main()
    {
        bool a[2]{true,false};
        cBool b{a[0]};
    }
    Es reicht Projekt.cpp zu kompilieren. Weitere build targets (bei C::B)
    sind nicht nötig.


    Wie willst du die darauf folgenden Schnittstellen innerhalb anderer Module bekannt machen?
    Wirst du außerhalb dieses Moduls immer mit Vorwärtsdeklarationen auskommen?
    Möchtest du alles immer gemeinsam durch den Compiler jagen?
    Was ist mit Fehlern durch Mehrfachdefinitionen (spätestens der Linker würde sich melden)?
    Das Projekt ist zu klein um in Shared Librarys (das meinst du beispiels-
    weise mit Modulen?) eingeteilt zu werden.
    Das wird sich zeigen, ich habe kein Konzept entworfen und mache alles at
    the fly
    . Vielleicht nicht die beste Art zu arbeiten, aber ich habe noch nie
    ein Projekt beendet und auch noch nie mit guter Dokumentation und gutem
    Gamedesign Erfahrungen gemacht. Und für dieses Projekt habe ich nurnoch
    ca. einen Monat Zeit und die will ich nicht damit verbringen, ein Design
    oder Konzept zu entwerfen. Dann lieber so viel coden wie möglich. Naiv,
    aber hoffentlich effektiv.
    Ja, bisher soll das so sein. Probleme mit Mehrfachdefinitionen kamen noch
    nicht vor.

    Danke für deinen Text zu const. Wie bereits erwähnt
    kenne ich mich mit Konstruktoren und der Veränderbarkeit, dem Kopieren usw.
    von Variabeltypen + Gültigkeitsbereichen noch lange nicht genug aus.


    Fragen, welche du dir zu Klassen stellen solltest, hier exemplarisch zu cPreyField:

    Was ist überhaupt ein Spielfeld?
    Was unterscheidet ein cPreyField von einem cField?
    Handelt es sich dabei um unterschiedliche Entitäten oder um unterschiedliche Eigenschaften derselben Entitäten?
    Werden Objekte des Typs cPreyField neben denen des Typs cField koexistieren?
    Ist cPreyField eine erweiternde Spezialisierung von cField?
    Wenn es sich um unterschiedliche Entitäten handelt, warum ist die Basisklasse nicht abstrakt?
    Wenn es sich nicht um unterschiedliche Entitäten, sondern Eigenschaften, handelt, warum sind die nicht nicht als Properties derselben Klasse angelegt?

    Ein eventueller Versuch, unterschiedliche Entitäten, also einerseits des Typs cField und andererseits des Typs cPreyField, ineinander überführen zu wollen, würde das Typsystem ad absurdum führen und die Sache nur unnötig verkomplizieren, wenn man den Gedanken zu Ende führt.

    Also gehe ich von der plausibelsten aller Varianten aus, dass ein cPreyField eine Spezialisierung von cField ist, welche sich unterschiedlich verhalten soll. Dann wäre jedoch, gemäß obiger Überlegungen, fix angelegt, welchen Typs die Felder jeweils sind, entweder ein unbesetzbares oder ein besetzbares Feld(?), solange das Spiel läuft. Soll es genau so funktionieren?
    Falls nicht, würde ich das noch mal überdenken. Falls zutreffend, kannst du es so machen. Dabei wäre zu bedenken, ob ein cPreyField wirklich nur ein besonderes cField ist. Wenn nicht, würde ich beide von einer abstrakten Basisklase erben lassen. Falls doch zutreffend, würde ich mir Gedanken machen, ob die Abstraktion stimmt, dass alle cField, ungeachtet der Tatsache, ob es sich dabei um ein cPreyField handeln könnte, grundsätzlich gleich behandelt werden sollen, indem es eben nur bei einem cPreyField ein besonderes Verhalten gibt. Falls das so ist, stehen die Chancen nicht schlecht, dass die Abstraktion passt.
    Deine Fragen sind gut, abstrakte Klassen habe ich total vergessen (da nie
    genutzt). Ich versuche mal diesen Fragenkatalog für alle meine Klassen
    durchzugehen und sie ggf. anzupassen.
    Zu cPreyField:
    Ein Spielfeld ist ein Objekt, welches einem Spieler zugeordnet wird und
    welcher nie wechselt. Es kann leer sein oder n beliebige Spielsteine
    enthalten. Die Spielsteine jeweils eines Feldes werden pro Zug auf null
    gesetzt. Die alte Menge an Spielsteinen wird sequentiell nach den geltenden
    Regeln in davor oder dahinter liegende andere Felder verteilt.
    Ein cPreyField ist besitzt die selben Eigenschaften, (inklusive,
    dass jedes cPreyField als cField beim Zug zählt) aus ihm kann jedoch nie
    etwas entfernt werden und er gibt auch keine Steine ab. Es sind Punktecounter
    der Spieler, die einen Zuwachs durch Spielzüge oder beim Schlagen erfahren.
    Die Verteilung von Steinen auf Felder läuft in den meisten fällen der Art:


    Beispiel für Zugmechanismus
    Code:
    struct tmpFiel{
        sn stonesIn=6;
        tmpFiel(sn a):
            stonesIn(a)
            {
            };
    };
    int main()
    {
        const int startfeld=2;
        tmpFiel field[10]={6,5,34,2,5,1,0,0,0,3}; //field[0] und field[9] wären in diesem Fall die beiden cPreyFields. Sie dürfen nie als startfeld gelten.
        const int iMax = field[startfeld].stonesIn;
        field[startfeld].stonesIn=0;
    
        for(int i           = startfeld,                       // counter für die Zugweite (=ehemaliger Inhalt des Startfelds - i)
                 virI        = startfeld,                       // Counter der nie größer wird als …
                 fieldsMax   = sizeof(field)/sizeof(tmpFiel); // die Anzahl an Array-Elementen
            i<iMax;
            ++i)                   // jede Runde wird das Feld
        {
            if(virI/fieldsMax!=0)
            {
                virI=0;
                std::cout << "\n";
            }
            ++field[++virI].stonesIn;
            std::cout << virI << ")\t" << field[virI].stonesIn << std::endl;
        }
    }
    [Bild: Bildschirmfoto_vom_2015_08_04_112825.png] Mir kam gerade die Idee, cPreyField könne man auch durch eine einfache
    Zählvariable der Spieler ersetzen die eben bei Spielstein-Verschiebung
    je nach Regeln erhöht werden oder nur in Ausnahmesituationen. Allerdings
    befindet sich dieser Zähler auf dem Spielbrett, als Spielfeld. Somit zählen
    cPreyField zur Anzahl an Spielfeldern, die auch pro Zug zuwachs ver-
    zeichnen. Das ist als counter umständlicher zu lösen, als wenn klar ist,
    dass sie im Kern cFields sind.
    Ergo passt die Umsetzung?

    Danke nochmal für das erklären von dem Sinn und Unsinn meines const-Wahns.
    Oparilames nachdem er seinen Gesellenbrief erhalten hat:
    »Das war's mit dir, du Mistvieh!«
    Oparilames ist offline

  7. #7 Zitieren
    Legende Avatar von jabu
    Registriert seit
    Jul 2011
    Beiträge
    7.363
    Also weiter im Text (Fortsetzung zu dem Bisherigen, vor deinem aktuellen Post):

    Code:
    //  Warum kann ich fields nicht in einem oder 2 Rutschs belegen?
    //  Warum scheint das nur über einzelne push_back zu gehen?  // Es geht auch anders!
    std::fill(fields.begin(), fields.end()-offset, unique_ptr<cField>(new cField(s,0)));
    ...weil std::fill eine Funktion ist und deshalb beim Aufruf genau einmal die Parameter übergeben bekommt. Es wird also versucht, die Elemente des Containers jeweils mit demselben std::unique_ptr zu besetzen, also Besitz an ihm mehrfach zu übernehmen, was gemäß seiner Aufgabe unterbunden wird. Du müsstest jedes Mal einen neuen std::unique_ptr erzeugen (welcher jeweils auf sein Objekt verweist).

    Code:
    std::fill(fields.begin(), fields.end()-offset, std::move(unique_ptr<cField>(new cField(s,0))));
    Genau dasselbe, siehe oben.

    Code:
    std::generate(fields.begin()+offset, fields.end(), unique_ptr<cField>(new cField(s,1)));
    std::generate bräuchte einen Funktionszeiger, um die Funktion aufrufen zu können, aber ein Funktionsname ist bereits ein Funktionszeiger (Edit: nur kein beschreibbarer, da es sich nur um einen Namen handelt, einen rvalue und eben keinen lvalue), weshalb das auch so einfach möglich ist:
    Code:
    // Template zur komfortablen Parameterübergabe,
    // denn std::generate erwartet einen Zeiger auf eine parameterlose Funktion,
    // weshalb man je Initialisierungsvariante, also für jede benötigte Kombination
    // aus den Parametern s und o in 'new cField(s, o)' eine eigene Funktion schreiben müsste,
    // da man diese Parameter nicht mitgeben kann...
    // Das Template nimmt uns diese Schreibarbeit ab und generiert diese spezialisierten
    // Funktionen für uns, je nach den Angaben in den Parametern.
    // Über das inline zerfallen diese ganzen Funktionen jeweils zu dem Code, als ob du ihn
    // an Ort und Stelle eingefügt hättest, wenn der Compiler es kann und will.
    // Es wird also ebenso effizienter Code produziert,
    // als hättest du kleinteilig deine Schleifen zum Auffüllen des Vectors geschrieben.
    // Aber auch ohne Angabe von inline kann der Compiler sich dazu entscheiden.
    // Dieses Optimierungsverhalten lässt sich über Compiler-Switches beeinflussen.
    
    template<sn s, bool o>
    inline std::unique_ptr<cField> CreateField_unique()
    {
        return std::unique_ptr<cField>(new cField(s, o));
    }
    
    void otherFunc()
    {
        //...
        // Eine Funktion zerfällt zu einem Zeiger auf diese, wenn nur ihr Name verwendet wird,
        // was bedeutet, dass die runden Klammern samt Argumentliste wegfallen:
        //
        std::generate(fields.begin()+offset, fields.end(), CreateField_unique<s, true>;
        //...
    }

    Mal sehen, womit ich jetzt weitermache...
    jabu ist offline Geändert von jabu (04.08.2015 um 15:13 Uhr)

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •