Copy-Konstruktor und Operatoren/Methoden



  • So,
    nachdem das vorhin so gut und schnell mit der Hilfe geklappt hat, stehe ich jetzt wieder vor einem Problem:

    Ich hab einen Copy-Konstruktor für meine Matrizen-Klasse gebastelt und eine Methode zum Addieren von Matrizen/ "+"-Operator überladen.
    Operator und Methode haben beide Matrix als Rückgabe-Typ.
    Nun dachte ich mir ich kann mit dem Copy-Konstruktor eine neues Objekt erstellen indem ich die aus der Addition resultierende Matrix übergebe ... geht natürlih nicht. Wenn ich aber eine vorher "normal" instanierte Matrix übergebe schluckt der Copy-Konstrukter die und abeitet normal.

    ...
    class Matrix
    {
        private:
        double** a;
        int Zeilen;
        int Spalten;
    
        public:
        Matrix (int, int, char, double);
        Matrix (Matrix&);
        ~Matrix ();
        Matrix Addition (Matrix&);
        Matrix operator + (Matrix&);
        void ZeilenTausch (int, int);
        void SpaltenTausch (int, int);
        void Ausgabe();
    };
    ...
    Matrix :: Matrix (Matrix& A) : Zeilen(A.Zeilen), Spalten(A.Spalten)
    {
        a = new double* [A.Zeilen];
        for (int i=0; i<A.Zeilen; i++)
        {
            a[i] = new double [A.Spalten];
    
            for (int j=0; j<A.Spalten; j++)
            {
                a[i][j] = A.a[i][j];
            }
        }
        cout << "\n Copy-Konstruktor\n";
    }
    ...
    int main ()
    {
    Matrix A(4,4);
    Matrix B(A); //geht wunderbar
    Matrix C(A.Addition(B)); //will er nicht
    Matrix D(A+B); //auch nicht
    

    error: no matching function for call to `Matrix::Matrix(Matrix)'|
    note: candidates are: Matrix::Matrix(Matrix&)|

    Ich steh also mal wieder auf dem Schlauch und hoffe ihr könnt mir nochmal helfen.



  • schmitzlipp schrieb:

    erstellen indem ich die aus der Addition resultierende Matrix übergebe ... geht natürlih nicht.

    Doch, das geht schon. Aber du bist einige Dinge falsch angegangen:

    1. Dein Kopierkonstruktor sollte keine Referenz nehmen, sondern eine Const-Referenz ( const Matrix& ). Dies aus zwei Gründen: Erstens veränderst du die übergebene Matrix nicht. Zweitens kann man auf diese Weise auch temporäre Objekte übergeben.

    2. Matrix Addition(Matrix&) : Mach das bitte nicht in C++. In Java macht man das z.B. so aufgrund mangelnder freier Funktionen. Hier hast du diese aber zur Verfügung, nutze sie also. Es muss nicht alles zur Klasse gehören.

    3. Gleiches gilt für den operator+ . Mach diesen global und biete in der Klasse einen operator+= an. So kannst du das globale + auf die Memberfunktion operator+= zugreifen lassen, ohne dass du friend oder ähnliches benutzt. Ausserdem lässt das implizite Konvertierungen des ersten Operanden zu.



  • Ok, vielen Dank für die Tipps.

    Ich muss euch hiernochmal loben, hier wird einem promt geholfen und man wird nicht gleich zu Beginn blöd angemacht von wegen wie dumm man doch sei und so.
    Echt gute Community!!



  • du kansnt auch mal bei den artikeln vorbeischauen - dort gibts auch 3 wunderbare artike über operatorüberladungen..

    bb



  • schmitzlipp schrieb:

    Ich steh also mal wieder auf dem Schlauch und hoffe ihr könnt mir nochmal helfen.

    Du kannst Dir diesen ganzen Hickhack (eigener Copy-Ctor, Destructor und Assignment) für die Resourcen-Verwaltung (hier: der reservierte Speicher für die Elemente) gänzlich sparen, indem Du einen std::vector verwendest.

    class Matrix
    {
      private:
        std::vector<double> elemente;
        int Zeilen;
        int Spalten;
    
        inline int index(int i, int j)
        {return i+j*Zeilen;} // oder j+i*Spalten, wie Du willst
    
      public:
        Matrix (int z, int s, double v)
        : elemente(z*s,v),
          Zeilen(z),
          Spalten(s) {}
    
        double   operator()(int i, int j) const
        {return elemente.at(index(i,j));}
    
        double & operator()(int i, int j)
        {return elemente.at(index(i,j));}
    
        Matrix& operator+=(Matrix const&);
    
        void ZeilenTausch (int, int);
        void SpaltenTausch (int, int);
    };
    
    Matrix& Matrix::operator+=(Matrix const& m)
    {
      if (Zeilen!=m.Zeilen || Spalten!=m.Spalten) throw .....
      .....
      return *this;
    }
    
    .....
    
    Matrix operator+(Matrix const& lhs, Matrix const& rhs)
    {
      Matrix result = lhs;
      lhs += rhs;
      return result;
    }
    

    Man beachte: Die vom Compiler automatisch generierten Elementfunktionen (copy-ctor, assignment, destructor) machen genau das Richtige. Statt "void Ausgabe" habe ich einfach mal zwei Elementfunktionen hinzugefügt, die einem den Zugriff auf die Elemente erlauben.

    Man kann diese Sache noch beliebig kompliziert werden lassen (siehe "Boost.uBLAS" Bibliothek http://www.boost.org/doc/libs/1_35_0/libs/numeric/ublas/doc/index.htm).

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Matrix operator+(Matrix const& lhs, Matrix const& rhs)
    {
      Matrix result = lhs;
      lhs += rhs;
      return result;
    }
    

    Upps... die mittlere Zeile sollte natürlich "result += rhs;" heissen.

    Gruß,
    SP



  • Man kann auch

    Matrix operator+(Matrix const& lhs, Matrix const& rhs)
    {
        return Matrix(lhs) += rhs;
    }
    

    schreiben. 😉

    (Zumindest wenn operator+= eine Referenz auf *this zurückgibt)...



  • Nexus schrieb:

    Man kann auch

    Matrix operator+(Matrix const& lhs, Matrix const& rhs)
    {
        return Matrix(lhs) += rhs;
    }
    

    schreiben. 😉

    (Zumindest wenn operator+= eine Referenz auf *this zurückgibt)...

    Klar kann man das. Aber es macht einen Unterschied. Bei Dir steht in der Return-Anweisung ein Funktionsaufruf der "irgendeine" Referenz zurückgiebt. Das erschwert die Return-Value-Optimierung unnötig. Schreibst Du dagegen

    Matrix operator+(Matrix const& lhs, Matrix const& rhs)
    {
        Matrix ret (lhs);
        ret += rhs;
        return ret;
    }
    

    ist dem Compiler klar, welches Objekt da zurückgegeben werden soll.

    Ich habe es gerade eben nochmal ausprobiert: g++ kann bei Deiner Kurzversion keine RVO durchführen. Mit der längeren Version sparst Du Dir eine Kopie des Matrix-Objektes.

    Gruß,
    SP



  • Sebastian Pizer schrieb:

    Klar kann man das. Aber es macht einen Unterschied. Bei Dir steht in der Return-Anweisung ein Funktionsaufruf der "irgendeine" Referenz zurückgiebt. Das erschwert die Return-Value-Optimierung unnötig.

    Wirklich? Ich hätte jetzt eher auf das Umgekehrte getippt, also dass ein kurzer temporärer Ausdruck viel eher wegoptimiert werden kann.

    Wie meinst du das mit irgendeiner Referenz? Es sollte dem Compiler doch klar sein, welcher RValue nicht kopiert werden muss. Oder etwa nicht?



  • Nexus schrieb:

    Wie meinst du das mit irgendeiner Referenz? Es sollte dem Compiler doch klar sein, welcher RValue nicht kopiert werden muss. Oder etwa nicht?

    Mit "irgendeiner" Referenz meinte ich die Tatsache, dass man der Signatur der operator+= Funktion

    class Matrix {
       ...
       Matrix& operator+=(Matrix const&);
       ...
    };
    

    nicht ansieht, was für eine Referenz diese Funktion zurückgegen wird. Es muss ja nicht eine Referenz auf *this sein. RVO kann nur dann funktionieren, wenn sich der Compiler sicher sein kann, dass der Ausdruck in der Return-Anweisung ein bestimmtes lokales Objekt referenziert. Das ist bei einem Funktionsaufruf, der eine Referenz zurückgiebt nicht gegeben -- es sei denn es handelt sich um eine Inline-Funktion und der Compiler ist übermäßig schlau (trifft auf g++ aber nicht zu).

    Gruß,
    SP



  • Ah, das macht natürlich Sinn. Ich habe bisher in C++ nicht wahnsinnig auf RVO Rücksicht genommen, in Zukunft werde ich wohl vermehrt darauf achten. Zumindest versuche ich, diesbezüglich etwas transparenter zu programmieren. 🙂

    Vielen Dank für die Erklärung!


Anmelden zum Antworten