Object call by reference



  • wollewausfander schrieb:

    Im Buch wird als Beispiel ein string addiert, der direkt als literal angegeben wird. Nun dachte ich, dass das string-objekt anonym erzeugt wird und ein Parameter des string-Konstruktors angegeben wird (also vectorobj.push_back("test"). Also ging ich davon aus, dass ich die Parameter des Konstruktors von Fieldcell übergeben muss. Letztlich muss ich also zuerst eine (nicht anonyme) Instanz von Fieldcell erzeugen und die an push_back übergeben?

    Das funktioniert bei Klassen, die einen impliziten Konstruktor mit einem Parameter besitzen, z.B. bei std::string .

    std::string::string(const char* c_str);
    

    Dann kannst du einen const char* übergeben, wo ein std::string gefordert ist, und es wird eine implizite Typkonvertierung durchgeführt. Man spricht auch von Konvertierungskonstruktor. Wenn der Konstruktor von String als explicit deklariert wäre, würde das nicht mehr funktionieren.

    Auf mehrere Parameter kannst du das nicht übertragen. Da musst du ein temporäres Objekt übergeben:

    battlefield.push_back(Fieldcell(0, PLAYERN));
    


  • @l'abra d'or, @Nexus,

    danke Euch für die Antwort. Ich hatte zwischenzeitlich

    Fieldcell newCell(0, PLAYERN);
    battlefield.push_back(newCell);
    

    benutzt, was auch funktioniert. Ist das "unschön", oder ohnehin das Gleiche wie

    battlefield.push_back(Fieldcell(0, PLAYERN));
    

    da bin ich mir garnicht so sicher, Letzteres sieht "anonymer" aus.



  • wollewausfander schrieb:

    da bin ich mir garnicht so sicher, Letzteres sieht "anonymer" aus.

    Ist es auch. 😉

    Während das temporäre Objekt nach der Zeile nicht mehr existiert, kannst du auf newCell immer noch zugreifen. Ist zwar eine potenzielle Fehlerquelle, aber keine schlimme. Wenn es dir in der Übersicht hilft, kannst du das Objekt ruhig separat deklarieren.



  • Nexus schrieb:

    Während das temporäre Objekt nach der Zeile nicht mehr existiert, kannst du auf newCell immer noch zugreifen. Ist zwar eine potenzielle Fehlerquelle, aber keine schlimme. Wenn es dir in der Übersicht hilft, kannst du das Objekt ruhig separat deklarieren.

    @Nexus: Ok, danke, die temporäre Lösung gefällt mir sogar besser.
    @l'abra d'or: Die Klasse heisst jetzt PlayingField. 😃

    Weitere Frage (ich hoffe, ich nerve nicht zuviel!):

    PlayingField::PlayingField(int rows, int cols) {
    
      m_Rows = rows;
      m_Columns = cols;
    
      // BattleArray.reserve(m_Rows * m_Columns);
    
      int k = 0;
      for(int i = 0; i < m_Rows; i++) {
        for(int j = 0; j < m_Columns; j++) {
          BattleArray.push_back(BattleArrayField(0, PLAYERN));      
          if (i == 0 || i == m_Rows - 1) {
            if (j == 0 || j == m_Columns - 1)
              BattleArray[k].setMaxstones(1);
            else
              BattleArray[k].setMaxstones(2);
          }
          else {
            if (j == 0 || j == m_Columns - 1)
              BattleArray[k].setMaxstones(2);
            else
              BattleArray[k].setMaxstones(3);
          }
          k++;
        }
      }
    }
    

    Ist es guter/schlechter Programmierstiel, solche Initialisierungen im Konstruktor zu machen? Und gibt es zweidimensionale vektoren?



  • wollewausfander schrieb:

    Und gibt es zweidimensionale vektoren?

    Ja, du kannst std::vector verschachteln. Oder sowas wie boost::multi_array verwenden.

    std::vector<std::vector<FieldCell> > myCells;
    // hier Grösse setzen
    myCells[x][y] = /* ... */;
    

    Alternativ 2D-Feld auf ein eindimensionales Array abbilden. Dann schreibst du dir eine Zugriffsfunktion, um eine 2D-Schnittstelle zu haben.

    std::vector<FieldCell> myCells;
    
    FieldCell& At(unsigned int x, unsigned int y)
    {
        return myCells[x + myWidth*y];
    }
    
    // Const-Überladung, wenn nur lesend zugegriffen wird
    const FieldCell& At(unsigned int x, unsigned int y) const;
    

    wollewausfander schrieb:

    Ist es guter/schlechter Programmierstiel, solche Initialisierungen im Konstruktor zu machen?

    Es ist durchaus guter Stil, wenn eine Klasse nach dem Konstruktoraufruf bereits fertig initialisiert ist. Was du allerdings tun könntest, ist die Initialisierungsliste statt Zuweisungen zu verwenden:

    PlayingField::PlayingField(int rows, int cols)
    : m_Rows(rows)
    , m_Columns(cols)
    {
       // ...
    }
    

    Wenn dein Konstruktorrumpf länger wird, könntest du die Schleifen auch in eine private Memberfunktion auslagern und diese Funktion im Konstruktor aufrufen.



  • Nexus schrieb:

    Alternativ 2D-Feld auf ein eindimensionales Array abbilden. Dann schreibst du dir eine Zugriffsfunktion, um eine 2D-Schnittstelle zu haben.

    std::vector<FieldCell> myCells;
    
    FieldCell& At(unsigned int x, unsigned int y)
    {
        return myCells[x + myWidth*y];
    }
    
    // Const-Überladung, wenn nur lesend zugegriffen wird
    const FieldCell& At(unsigned int x, unsigned int y) const;
    

    Danke. Was wird denn hier zurück gegeben? Eine Referenz auf das Objekt, oder eine Kopie des Objekts?



  • Eine Referenz auf das Objekt.



  • drakon schrieb:

    Eine Referenz auf das Objekt.

    Vielen Dank.

    In folgendem Code

    void PlayingField::recursiveCascade() {
        bool isOverflow = false;
        int row = 0;
        int col = 0;
        int k = 0;
        char color = ' ';
        vector<BattleArrayField>::const_iterator myIterator;
    
        for (myIterator = BattleArray.begin(); myIterator != BattleArray.end(); myIterator++) {
            if ((*myIterator).isOverflow()) {
                color = (*myIterator).getColor();
                isOverflow = true;
                row = k / m_Columns;
                col = k % m_Columns;
                // cout << "Zeile: " << row << ", Spalte: " << col << endl;
                if (row == 0) {
                    if (col == 0) {
                        BattleArray[k].decStones();
                        BattleArray[k+1].incStones();
                        ...
                        ...
                   }
                }
            }
            k++;
        }
        if (isOverflow)
          recursiveCascade();
    }
    

    hatte ich Zeile 28 zuvor so:

    this.recursiveCascade();
    

    da ich mich ja in einer PlayingField-Instanz befinde. Dies wurde aber vom Compiler beanstandet:

    PlayingField.cpp: In member function ‘void PlayingField::recursiveCascade()’:
    PlayingField.cpp:189: error: request for member ‘recursiveCascade’ in ‘this’, which is of non-class type ‘PlayingField* const’
    make: *** [PlayingField.o] Fehler 1

    Das kann ich mir garnicht erklären. Und dann noch:

    void PlayingField::setStone(const Player& player, const string& coord) {
    
      // Bsp: coord = "C5"
      // Im header: alpha_index = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
      const char * scolumn = &coord[1];
      int column = atoi(scolumn) - 1;  
    
      char srow = coord[0];
      size_t rowsearch = alphaIndex.find(srow);
      int row = (int)rowsearch;
    
      int k = row * m_Columns + column;
    
      cout << "Zeile: " << row << ", Spalte: " << column << ", Feld: " << k << endl;
    
      if (!BattleArray[k].isOpponent(player) || BattleArray[k].isEmpty()) {
      ....
    

    Zeile 5 hatte ich ursprünglich so:

    char scolumn = coord[1];
    

    was aber beanstandet wurde, was ich mir nicht erklären kann. Und überhaupt; gibt es einen besseren Weg, coord zu dekodieren?



  • this ist eine Zeiger!

    // this.recursiveCascade();
    this->recursiveCascade();
    

    Ansonsten ist das ein Konstanter Zeiger auf ein PlayingField (1) und nicht ein Zeiger auf ein konstantes PlayingField (2).

    PlayingField const *ptr1; // (1)
    const PlayingField *ptr2; // (1)
    PlayingField * const ptr3; // (2)
    


  • wollewausfander schrieb:

    Zeile 5 hatte ich ursprünglich so:

    char scolumn = coord[1];
    

    was aber beanstandet wurde, was ich mir nicht erklären kann. Und überhaupt; gibt es einen besseren Weg, coord zu dekodieren?

    1. "beanstandet wurde" ist keine Fehlermeldung. Die brauchen wir aber um das Problem korrekt einschätzen zu können.
    2. Nehm ich dir das Statement nicht ab.
      Ich nehme eher an, das sah vorher so aus:
    char* scolumn = &coord[1];
    

    Und das geht eben wegen dem kleinen dummen const wieder nicht 😉
    http://www.cplusplus.com/reference/string/string/operator[]/
    Denn die Zeile wie du sie da stehen hast kompiliert einwandfrei.

    Überhaupt den Link http://www.cplusplus.com/reference/ gut aufheben, die Doku ist das wichtigste Werkzeug für dich, ohne der bist du aufgeschmissen 😛



  • l'abra d'or schrieb:

    1. "beanstandet wurde" ist keine Fehlermeldung. Die brauchen wir aber um das Problem korrekt einschätzen zu können.

    Sorry:

    PlayingField.cpp: In member function ‘void PlayingField::setStone(const Player&, const std::string&)’:
    PlayingField.cpp:196: error: invalid conversion from ‘char’ to ‘const char*’
    PlayingField.cpp:196: error:   initializing argument 1 of ‘int atoi(const char*)’
    make: *** [PlayingField.o] Fehler 1
    

    l'abra d'or schrieb:

    1. Nehm ich dir das Statement nicht ab.
      Ich nehme eher an, das sah vorher so aus:
    char* scolumn = &coord[1];
    

    Und das geht eben wegen dem kleinen dummen const wieder nicht 😉
    http://www.cplusplus.com/reference/string/string/operator[]/
    Denn die Zeile wie du sie da stehen hast kompiliert einwandfrei.

    Als Anfänger widerspreche ich natürlich nicht gerne, aber es ist leider genau umgekehrt. Nur mit

    const char* scolumn = &coord[1];
    

    läuft alles ohne Meldung durch.

    l'abra d'or schrieb:

    Überhaupt den Link http://www.cplusplus.com/reference/ gut aufheben, die Doku ist das wichtigste Werkzeug für dich, ohne der bist du aufgeschmissen 😛

    Den habe ich in der Linkliste, aber mein Buch liegt auch hier auf dem Tisch. 🙂



  • l'abra d'or schrieb:

    this ist eine Zeiger!

    // this.recursiveCascade();
    this->recursiveCascade();
    

    Oh ja, stimmt ja, sorry.

    l'abra d'or schrieb:

    Ansonsten ist das ein Konstanter Zeiger auf ein PlayingField (1) und nicht ein Zeiger auf ein konstantes PlayingField (2).

    PlayingField const *ptr1; // (1)
    const PlayingField *ptr2; // (1)
    PlayingField * const ptr3; // (2)
    

    Woher weiss man (weiss ich) denn, dass es sich um einen konstanten Zeiger handelt? Oder sind this-Zeiger immer konstant?



  • wollewausfander schrieb:

    Oder sind this-Zeiger immer konstant?

    Genau genommen sind es skalare RValues, und diese können nicht verändert werden. Das ist wie wenn du 3 = 4 schreibst.

    Befindest du dich zudem in einer const -Methode, zeigt this auf ein konstantes Objekt, ansonsten nicht.

    void MyClass::MemberFunction()
    {
        this; // Typ MyClass*
    }
    
    void MyClass::MemberFunction() const
    {
        this; // Typ const MyClass*
    }
    


  • wollewausfander schrieb:

    Als Anfänger widerspreche ich natürlich nicht gerne, aber es ist leider genau umgekehrt. Nur mit

    const char* scolumn = &coord[1];
    

    läuft alles ohne Meldung durch.

    Die Fehlermeldung spricht aber ein anderes Wort 😉
    Dass "const char* c = &coord[1];" geht bezweifle ich nicht.
    Dass "char c = coord[1];" hingegen einen Fehler gibt - jenen von dir geposteten - bezweifle ich sehr wohl!
    Deiner Fehlermeldung zufolge machst du so etwas:

    const char* c = s[0];
    

    Was willst du jetzt eigentlich genau haben, einen char oder einen (const) char*?

    BTW: eine Klasse "FieldIndex" wäre nicht schlecht. Denn die Koordinate in allen benötigten Funktionen erst aus nem String zu extrahieren ist witzlos 😃



  • l'abra d'or schrieb:

    Was willst du jetzt eigentlich genau haben, einen char oder einen (const) char*?

    Ich habe mich vermutlich falsch ausgedrückt:

    void PlayingField::setStone(const Player& player, const string& coord) {
    
      // const char * scolumn = &coord[1];
      // char scolumn = coord[1];
      int column = atoi(scolumn) - 1;
      ...
    

    Beide kommentierten Zeilen funktionieren. Aber die Fehlermeldung entsteht, wenn ich die zweite Zeile in Kombination mit der dritten Zeile compiliere. Mir ist klar, dass atoi einen Zeiger auf eine char erwartet, aber ich dachte, der Ausdruck in der zweiten Zeile wäre ebenfalls ein Zeiger auf ein char. Hab mich aber vertan, denn nur coord ist ein Zeiger, aber coord[1] nicht, oder?

    l'abra d'or schrieb:

    BTW: eine Klasse "FieldIndex" wäre nicht schlecht. Denn die Koordinate in allen benötigten Funktionen erst aus nem String zu extrahieren ist witzlos 😃

    Sollte diese Klasse dann nur aus statischen Methoden bestehen, oder wie werden die Instanzen dieser Klasse dann benutzt?

    Ich kann auch gerne mal den kompletten code irgendwo hin pasten, falls jemand mal drübersehen will, was man hätte besser machen können, kann aber verstehen, wenn/dass dazu sicher niemand lust hat. 🙂 Falls doch, wo kann ich es pasten?



  • wollewausfander schrieb:

    Beide kommentierten Zeilen funktionieren. Aber die Fehlermeldung entsteht, wenn ich die zweite Zeile in Kombination mit der dritten Zeile compiliere. Mir ist klar, dass atoi einen Zeiger auf eine char erwartet, aber ich dachte, der Ausdruck in der zweiten Zeile wäre ebenfalls ein Zeiger auf ein char. Hab mich aber vertan, denn nur coord ist ein Zeiger, aber coord[1] nicht, oder?

    Fast. Du erkennst den Zeiger an dem "" :p. coord selber ist ein std::string!
    coord.c_str(); ist ein const char
    mit den Daten.

    Sollte diese Klasse dann nur aus statischen Methoden bestehen, oder wie werden die Instanzen dieser Klasse dann benutzt?

    Nein, du willst ja dann an PlayingField::setStone() ein Objekt der Klasse FieldIndex übergeben 😉

    #include <string>
    #include <sstream>
    #include <iostream>
    
    struct FieldIndex {
        size_t x, y;
        explicit FieldIndex(const std::string& pos) {
            const std::string alpha_index = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            std::stringstream str(pos);
            char cx;
            size_t newX, newY;
            str >> cx;
            newX = alpha_index.find(cx);
            str >> newY;
            x = newX;
            y = newY - 1;
        }
    };
    
    std::ostream& operator<<(std::ostream& s, const FieldIndex& i) {
        std::cout << "X: " << i.x << "; Y: " << i.y;
    }
    
    int main() {
        std::string str;
        std::cin >> str; // Eingabe: A1 -> x=0, y=0
        FieldIndex idx(str);
        std::cout << idx << std::endl;
    }
    

    So in etwa. Du solltest dann natürlich erst noch überprüfen, ob wirklich alles korrekt zu geht 😉



  • l'abra d'or schrieb:

    Fast. Du erkennst den Zeiger an dem "" :p. coord selber ist ein std::string!
    coord.c_str(); ist ein const char
    mit den Daten.

    Das heisst, es würde auch folgendes gehen?:

    const char * scolumn = coord.c_str();
    scolum++; //Wegen Index [1]
    

    l'abra d'or schrieb:

    Nein, du willst ja dann an PlayingField::setStone() ein Objekt der Klasse FieldIndex übergeben 😉

    #include <string>
    #include <sstream>
    #include <iostream>
    
    struct FieldIndex {
        size_t x, y;
        explicit FieldIndex(const std::string& pos) {
            const std::string alpha_index = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            std::stringstream str(pos);
            char cx;
            size_t newX, newY;
            str >> cx;
            newX = alpha_index.find(cx);
            str >> newY;
            x = newX;
            y = newY - 1;
        }
    };
    
    std::ostream& operator<<(std::ostream& s, const FieldIndex& i) {
        std::cout << "X: " << i.x << "; Y: " << i.y;
    }
    
    int main() {
        std::string str;
        std::cin >> str; // Eingabe: A1 -> x=0, y=0
        FieldIndex idx(str);
        std::cout << idx << std::endl;
    }
    

    So in etwa. Du solltest dann natürlich erst noch überprüfen, ob wirklich alles korrekt zu geht 😉

    Oh, hier tauchen aber jetzt ein paar Fragen auf. 🙂

    struct und class sind synonym, soviel weiss ich schon (oder hast Du aus einem anderen Grund struct benutzt?)?
    Was heisst denn explicit?
    Und warum kann man code direkt in der Klassendeklaration schreiben?
    Muss das Überladen des << Operators nicht als friend-funktion in FieldIndex bekannt gemacht werden?

    str >> newY;
    

    geht einfach so, ohne typcasting?

    Das Überladen des << Operators ist in meinem Fall nicht nötig, ich hatte die Ausgabe von Zeile/Spalte nur als Debugging drin, sorry, dass Du Dir die extra Mühe gemacht hast. 😃

    Vielen Dank!



  • wollewausfander schrieb:

    Das heisst, es würde auch folgendes gehen?:

    const char * scolumn = coord.c_str();
    scolum++; //Wegen Index [1]
    

    Probiers doch aus :p

    struct und class sind synonym, soviel weiss ich schon (oder hast Du aus einem anderen Grund struct benutzt?)?

    struct und class sind in C++ fast identisch, einzig ist bei struct defaultsichtbarkeit der Member auf public, class ist auf private.
    Ich habe struct gewählt, weils schneller geht 😉
    Du könntest jetzt argumentieren, dass class besser ist, weil ich den Index nur aus dem String lesen will und danach nicht mehr verändern. Damit wäre die Klasse besser gekapselt, wenn ich x und y private mache und nur über getter Zugriff gewähre aber keine setter anbiete.
    Aber das war mir für das schnelle Beispiel ehrlich gesagt egal 😉

    Was heisst denn explicit?

    Dass du immer explizit den Konstruktor aufrufen musst, eine implizite Konvertierung wird nicht durchgeführt.

    PlayingField f;
    f.setStone( player, std::string("A1") ); // FEHLER!! Ging aber ohne explicit
    f.setStone( player, FieldIndex(std::string("A1")) ); // Korrekt
    

    Implizite Konvertierungen sind manchmal der Grund für ewiges, frustrierendes Debuggen.

    Und warum kann man code direkt in der Klassendeklaration schreiben?

    Such dich mal nach "inline" schlau.

    Muss das Überladen des << Operators nicht als friend-funktion in FieldIndex bekannt gemacht werden?

    Nein, warum? Ich greif aus operator<< ja nicht auf private Member zu, und nur dafür bräuchte man ein friend.

    str >> newY;
    

    geht einfach so, ohne typcasting?

    Ja, warum sollte es nicht gehen? Was hättest du erwartet?



  • l'abra d'or schrieb:

    Ich habe struct gewählt, weils schneller geht 😉
    Du könntest jetzt argumentieren, dass class besser ist...

    Auf keinen Fall, dazu habe ich viel zuwenig Erfahrung. 🙂

    l'abra d'or schrieb:

    Aber das war mir für das schnelle Beispiel ehrlich gesagt egal 😉

    Gefällt mir aber ganz gut.

    l'abra d'or schrieb:

    Was heisst denn explicit?

    Dass du immer explizit den Konstruktor aufrufen musst, eine implizite Konvertierung wird nicht durchgeführt.

    PlayingField f;
    f.setStone( player, std::string("A1") ); // FEHLER!! Ging aber ohne explicit
    f.setStone( player, FieldIndex(std::string("A1")) ); // Korrekt
    

    Implizite Konvertierungen sind manchmal der Grund für ewiges, frustrierendes Debuggen.

    Hmmm...so ganz kapiere ich nicht. Ich wollte das Ganze in meinen Code einbauen. Die struct habe ich in ein extra File übernommen. Da es keine Headerdatei gibt, weiss ich a) nicht, wie mein Makefile an der Stelle aussehen muss, und b) wie ich die struct im aufrufenden File bekannt mache:

    Cascade.cpp
    -----------
        ...
        string choice;
        ...
        cin >> choice;
    
        if (currentColor == firstPlayer.getColor())
          myField.setStone(firstPlayer, FieldIndex(choice));
        else
        ...
    
    PlayingField.cpp
    ----------------
    ...
    void PlayingField::setStone(const Player& player, FieldIndex idx) {
    
      int k = idx.row * m_Columns + idx.column;
      // cout << "Zeile: " << idx.row << ", Spalte: " << idx.column << ", Feld: " << k << endl;
      ...
    
    Makefile:
    ---------
    CC = g++
    
    LDLIBS =
    CFLAGS =
    
    OBJ = Cascade.o Player.o BattleArrayField.o PlayingField.o FieldIndex.o
    
    Cascade : $(OBJ) Cascade.h
    	$(CC) $(OBJ) $(LDLIBS) -o Cascade
    
    Cascade.o : Player.h PlayingField.h
    	$(CC) $(CFLAGS) -c Cascade.cpp
    
    Player.o : Player.h Cascade.h
    	$(CC) $(CFLAGS) -c Player.cpp
    
    BattleArrayField.o : BattleArrayField.h Player.h Cascade.h
    	$(CC) $(CFLAGS) -c BattleArrayField.cpp
    
    PlayingField.o : PlayingField.h BattleArrayField.h Player.h Cascade.h
    	$(CC) $(CFLAGS) -c PlayingField.cpp
    
    FieldIndex.o : Cascade.h
    	$(CC) $(CFLAGS) -c FieldIndex.cpp
    
    all : Cascade
    
    clean : 
    	rm $(OBJ) Cascade
    

    So kommt dies:

    In file included from Cascade.cpp:5:
    PlayingField.h:18: error: ‘FieldIndex’ has not been declared
    Cascade.cpp: In function ‘int main()’:
    Cascade.cpp:56: error: ‘FieldIndex’ was not declared in this scope
    Cascade.cpp:58: error: ‘FieldIndex’ was not declared in this scope
    make: *** [Cascade.o] Fehler 1
    

    l'abra d'or schrieb:

    str >> newY;
    

    geht einfach so, ohne typcasting?

    Ja, warum sollte es nicht gehen? Was hättest du erwartet?

    Eine Fehlermeldung, dass man einer int variablen keinen string zuweisen kann?!



  • wollewausfander schrieb:

    Ich wollte das Ganze in meinen Code einbauen. Die struct habe ich in ein extra File übernommen. Da es keine Headerdatei gibt, weiss ich a) nicht, wie mein Makefile an der Stelle aussehen muss, und b) wie ich die struct im aufrufenden File bekannt mache:

    Dann pack die Klassendefinition (nicht die Definition der Member-Funktionen!) in eine Header-Datei. Diese includieren wo sie benötigt wird.

    BTW. könntest du dir mal nen Makefile-Generator anschauen. CMake find ich ganz praktisch, gibt aber noch viele andere wie boost.build, scons, usw. Da schreibst du eine kurze Datei in nem bestimmten Format, und der Generator erstellt Makefiles für verschiedenste Plattformen, Compiler, usw. automatisch. Hinzu kommen bei einigen Mechanismen um Abhängigkeiten zu finden. Dann ist es richtig einfach, Include-Verzeichnisse zu richtig einzubinden oder gegen die korrekte lib zu linken.


Anmelden zum Antworten