gedanklicher Fehler beim Überladen des Zuweisungsoperators



  • Hallöchen mal wieder 🙂

    Ich will/soll eine Klasse Matrix programmieren und deren Operatoren als auch den Zuweisungsoperator operator= überladen. Ich denke ich habe das bisher ganz gut hinbekommen, allerdings scheitert das ganze jetzt irgendwie an meinem Zuweisungsoperator. Innerhalb meiner Funktioin wird die Matrix richtig berechnet, wenn ich allerdings wieder in die main zurück springe, sind die Daten alle wieder auf 0. Irgendwo scheint mir da ein Denkfehler unterlaufen zu sein:

    Matrix Matrix::operator= (const Matrix &matrix) 
    {
    	if (this != &matrix) {
    		return matrix;
    	} // if (this != &matrix) {
    } // Matrix Matrix::operator= (const Matrix &matrix)
    
    Matrix Matrix::operator+ (const Matrix &matrix) 
    {
    	Matrix result;
    	for (int i=0; i<5; i++) 
    	{
    		for (int j=0; j<5; j++) 
    		{
    			result.werteMatrix[i][j] = this->werteMatrix[i][j] + matrix.werteMatrix[i][j];
    		} // for (int j=0; j<5; j++) 
    	} // for (int i=0; i<5; i++) 
    	return result;
    } // Matrix Matrix::operator+ (const Matrix &matrix)
    

    Wenn ich zwei Matrizen zusammen addiere und bei der Überwachung mir die Werte innerhalb von result anschaue, dann ist alles richtig berechnet worden und die Werte stehen in result drin.
    Dann kommt der Zuweisungsoperator, auch hier sind die Daten ordnungsgemäß im übergebenen Parameter &matrix gelandet. Allerdings wenn ich jetzt mit dem return aus der Funktion heraus komme und in der main lande, dann ist die Zielmatrix, also die, der dieser Wert zugewiesen werden soll (matrixC), leer und die Daten sind alle verschwunden.

    main.cpp:
    	Matrix matrixA;
    	Matrix matrixB;
    	Matrix matrixC;
    
    	[...]
    
    	matrixC = matrixA + matrixB;
    

    Wo liegt hier mein Denkfehler?

    mit freundlichen Grüßen
    kruemeltee



  • Du brauchst einen CopyKonstruktor. Schau dir auch mal das hier an
    http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap



  • brauch ich den grundsätzlichst oder nur, wenn Attribute Zeiger enthalten? Ich hab ein bisschen gelesen, wie ein CopyConstructor funktioniert ... ich hoffe ich hab das richtig verstanden.
    Ein CopyConstructor reserviert neuen Speicherplatz für seine Attribute" und kopiert dann die Attribute vom dem als Parameter übergebenen Objekt, oder? Sprich wenn ich als Attribut eines Klasse einen Zeiger auf etwas habe, dann muss ich diesen neu initialisieren und die Daten aus dem übergebenen Objekt rüber kopieren. Oder?

    Ich hab ja nun zwei Objekte ... einmal das Objekt Matrix, welches ein zweidimensionales Array (keinen Zeiger auf ein Array) vom Typ Ratio (ebenfalls eine Klasse mit zwei double Attributen) besitzt. Bei welchem von beiden brauch ich dann einen CopyConstructor? (bei dem Elementtyp Ratio, bei der Matrix oder gar bei beiden?)
    Und viel besser die Frage, da ich ja keine Zeiger nutze, wie reserviere ich dann neuen Speicher für die Attribute? new kommt ja nicht in Frage, oder?

    mfg kruemeltee



  • also ich habe jetzt bei beiden einen Kopierkonstruktor geschrieben und einfach die Daten herüber kopiert. Nun versuche ich einmal Schrittweise die Funktion durch Debuggen schrittweise zu verstehen ...

    dabei fällt mir eine Sache auf, und zwar daß ich im gesamten Vorgang scheinbar 3 Objekte habe ... aber vielleicht ist auch hier wieder nur ein gedanklicher Fehler drin ...

    Matrix Matrix::operator= (const Matrix &matrix) 
    {
    	if (this != &matrix) {
    		return matrix;
    	} // if (this != &matrix) {
    } // Matrix Matrix::operator= (const Matrix &matrix) 
    
    Matrix::Matrix(const Matrix& matrix)
    {
    	for (int i = 0; i<5; i++) 
    	{
    		for (int j = 0; j<5; j++) 
    		{
    			werteMatrix[i][j] = matrix.werteMatrix[i][j];
    		} // for (int j = 0; j<dimensiony; j++) 
    	} // for (int i = 0; i<dimensionx; i++) 
    } // Matrix::Matrix(const Matrix& matrix)
    

    wenn ich innerhalb des Zuweisungsoperators bin, hat in einem nachvollziehbarem Beispiel zunächst einmal die Rechnung mit dem Addieren erst einmal funktioniert. Das Ergebnis landet also in meinem Parameter von operator= .

    folgende Speicherstellen finde ich nun zunächst einmal in der überladenen Operatorfunktion vor:

    Matrix& matrix (Parameter mit dem richtigen Ergebnis) liegt an Stelle 0x37f3fc

    this (die Matrix von der der Operator = aufgerufen wurde, also vermutlich im Hauptprogramm nachher matrixC ) liegt an Stelle 0x37efac.

    Daher hatte ich eigentlich angenommen, daß wenn ich in den Kopierkonstruktor von Matrix komme, ich es dann ebenfalls mit diesen beiden Objekten zu tun habe. (also der als Parameter übergebene mein Objekt mit dem richtigen Ergebnis und als Ziel ( this ) den zukünftigen, also ebenfalls den this aus der überladenen Operatorfunktion =.

    im Kopierkonstruktor sieht es nun überhaupt nicht so aus:
    Hier taucht nun ein komplett neuer auf, denn der this Zeiger zeigt auf die Speicherstelle 0x37f144. Der Parameter hat die selbe Speicherstelle wie der ursprüngliche in der Operatorfunktion. Wo kommt denn dieser nun her?

    mfg kruemeltee



  • kruemeltee schrieb:

    brauch ich den [copy constructor] grundsätzlichst oder nur, wenn Attribute Zeiger enthalten?

    So, in etwa. Kommt wirklich drauf an, wie Du den Rest implementierst und was für ein Verhalten die Klasse haben soll. Soll sie sich so verhalten wie ein Zeiger oder doch eher wie die Typen int, double, ... ("Wertsemantik")?

    kruemeltee schrieb:

    einmal das Objekt Matrix, welches ein zweidimensionales Array (keinen Zeiger auf ein Array) vom Typ Ratio (ebenfalls eine Klasse mit zwei double Attributen) besitzt.

    Dann brauchst Du weder copy constructor, noch assignment operator, noch destructor. Das gleiche gilt auch für die Nutzung der STL container. Wenn Du also statt Ratio elemente[5][5]; einfach std::vector<Ratio> elemente; verwendest, ist die Sache ähnlich schmerzfrei. Die vom Compiler automatisch generierten copy constructors, assignment operators und destructors machen genau das Richtige.

    Gruß,
    SP



  • Würde ich so gar nicht machen. Matrix::op + würde bei mir Addition mit einer Konstanten sein. Die Addition würde ich als

    Matrix operator+(Matrix const & m1, Matrix const & m2)
    

    implementieren



  • das heisst also als Operatormethode und nicht als Operatorfunktion? Worin besteht der Unterschied in meinem Fall? Daß ich direkt über die beiden Parameter auf die übergebenen zugreifen kann?

    Mein problem scheint ja nicht in der Addition zu liegen sondern vielmehr im Zuweisungsoperator. Würde es etwas ändern, wenn ich das ganze auch als Operatormethode implementieren würde?

    mfg kruemeltee



  • Du hast auch noch einen Gedankenfehler in deinem operator=.
    Was du willst ist ja Folgendes:

    Matrix m1;
    // m1 mit Werten füttern
    Matrix m2 = m1;
    

    Danach sollten die Daten von m2 denen von m1 entsprechen, also idealerweise gilt
    m2 == m1
    Dein operator= sollte also die selbe Arbeit wie der Kopierkonstruktordurchführen, nämlich die Daten kopieren.
    Du veränderst dein Objekt im Moment überhaupt nicht, es wird nur die übergebene Matrix weitergereicht. Bringt ja so nix 😉



  • okay .. ich bin ein Stückchen weiter ... der Kopierkonstruktor wird doch nur dann ausgeführt, wenn ich bei der Zuweisung (also nem simplen 🙂 auf der linken Seite ein noch nicht initialisiertes Objekt habe bzw. gerade am Initialisieren bin.

    daher sollte

    Matrix matrixA;
    // matrixA mit Werten füttern
    Matrix matrixB = matrixA; // a.)
    Matrix *matrixB = new Matrix(matrixA); // b.)
    

    beinahe identisch sein (mit dem unterschied, daß hier einmal ein Zeiger und einmal ein normales Objekt vorhanden ist). Quasi sorgt der Zuweisungsoperator im Falle a.) für eine Neuinitialisierung von matrixB und füttert diese (wenn der Kopierkonstruktor vorhanden ist und funktioniert) mit den Werten von MatrixA .

    lediglich wenn matrixB bereits initialisiert ist kommt der Kopierkonstruktor gar nicht zum Einsatz und es bleibt bei einer einfachen Zuweisung. Daher habe ich jetzt folgendes beim Zuweisungsoperator geändert/hinzugefügt

    Matrix Matrix::operator= (const Matrix &matrix) {
    	if (this != &matrix) {
    		for (int i=0; i<5; i++) {
    			for (int j=0; j<5; j++) {
    				this->werteMatrix[i][j] = matrix.werteMatrix[i][j];
    			} // for (int j=0; j<5; j++) {
    		} // for (int i=0; i<5; i++) {
    		return matrix;
    	} // if (this != &matrix) {
    } // Matrix Matrix::operator= (const Matrix &matrix) {
    

    also habe ich das Kopieren erst einmal bereits beim Operator gemacht. Und das scheint auch zu funktionieren.

    jetzt meine Frage: ist diese Art des "Kopierens" nun auch per Kopierkonstruktor möglich? Denn der einzige Knackpunkt bei mir ist ja der, daß wenn ich noch innerhalb dieser operation (=) bin, die gewünschten Ergebnisse in der Return matrix enthalten sind, allerdings einen Schritt weiter, also von dort, wo diese Operation aufgerufen wurde (nämlich bei matrixC = matrixA + matrixB; ) stimmt es nicht mehr, quasi wurde der return Wert nicht ordnungsgemäß an MatrixC übergeben. Das ist jetzt der Punkt, den ich nicht verstehe ...

    mfg kruemeltee



  • Beachte den Beitrag von New Neo.



  • deswegen hab ich ja den Kopiervorgang, wie er auch im Kopierkonstruktor steht, mit in die Operation eingefügt und wollte eigentlich nur wissen, ob ich das tatsächlich zwei mal schreiben muss oder nicht gleich von vorn herein dem Kopierkonstruktor komplett überlassen kann 😉

    mfg kruemeltee



  • kruemeltee schrieb:

    Matrix Matrix::operator= (const Matrix &matrix) {
    if (this != &matrix) {
    		for (int i=0; i<5; i++) {
    			for (int j=0; j<5; j++) {
    				this->werteMatrix[j] = matrix.werteMatrix[i][j];
    			} // for (int j=0; j<5; j++) {
    		} // for (int i=0; i<5; i++) {
    		return matrix;
    	} // if (this != &matrix) {
    } // Matrix Matrix::operator= (const Matrix &matrix) {
    

    Lieber kruemeltee,
    *

    • Die operator= Elementfunktion hat *this als [i]Referenz zurückzugeben. Nichts anderes!
    • Wie schon gesagt brauchst Du weder copy constructor, assignment operator noch destructor, da werteMatrix bei Dir ein 2D array ist und die vom Compiler automatisch generierten Versionen für diese Funktionen hier das Richtige tun.

    Du solltest mal wieder in (D)ein C++ Buch schauen und nachlesen, wie das alles genau funktioniert.

    Gruß,
    SP



  • diesen Hinweis werde ich gern beherzigen, da die einzigen Quellen derzeit das Internet waren (also Foren, How-Tos usw.), mal sehen was das schlaue Buch dazu sagt ...
    In allen bisherigen Quellen war jedoch bei Operatorfunktionen davon die Rede, daß der einzige Parameter der übergeben wird der auf den die Funktion auslösenden Pameter folgende ist. Also bei einer Zuweisung i.d.R. der auf der rechten Seite. Wenn ich allerdings immer noch mein Funktionsdenken (was ich leider nicht so schnell ablegen kann) berücksichtige, dann wird doch der Returnwert der Funktion dem letztendlichen Ziel (also der linken Seite) zugewiesen.
    Allerdings scheint jetzt hier mein gedanklicher Fehler zu sein ... denn der this- Zeiger innerhalb der Operatorfunktion scheint bereits die linke Seite zu sein, von daher brauchte ich also nur den Inhalt vom Parameter (die rechte Seite) in den this-Zeiger "kopieren" ... allerdings macht es dann bei der Ausführung dieser "Funktion" keinen unterschied, ob ich am Ende ein return *this; oder ein return matrix; durchführe. Aber um das genauer zu verstehen, werde ich mich nochmals in die Lektüre verkrümeln ...

    trotzdem vielen Dank Sebastian 🙂

    mit freundlichen Grüßen
    kruemeltee



  • kruemeltee schrieb:

    In allen bisherigen Quellen war jedoch bei Operatorfunktionen davon die Rede, daß der einzige Parameter der übergeben wird der auf den die Funktion auslösenden Pameter folgende ist. Also bei einer Zuweisung i.d.R. der auf der rechten Seite.

    Dies im schlauen Buch nachlesen.

    kruemeltee schrieb:

    Wenn ich allerdings immer noch mein Funktionsdenken (was ich leider nicht so schnell ablegen kann) berücksichtige, dann wird doch der Returnwert der Funktion dem letztendlichen Ziel (also der linken Seite) zugewiesen.

    Returnwert welcher Funktion? operator=? operator= IST ja gerade die Zuweisung. Folgende zwei Aufrufe sind bei Benutzer-definierten Typen äquivalent:

    a = b;
      a.operator=(b);
    

    kruemeltee schrieb:

    allerdings macht es dann bei der Ausführung dieser "Funktion" keinen unterschied, ob ich am Ende ein return *this; oder ein return matrix; durchführe.

    Doch. Es ist eine Konvention, die Objekte Deiner Matrix-Klasse ähnlich wie zB ints bzgl Zuweisung behandeln lässt:

    a = b = c = d;
    

    Gruß,
    SP


Log in to reply