operator überladen so richtig?



  • Nur über die Variante mit der freien Funktion kannst du einen symmetrischen operator+ definieren, wenn du die Addition mit anderen Typen (insbesondere fundamentale Typen) zulassen willst (lhs wäre dann ein fundamentaler Typ und rhs die Matrix). Ob das in jedem Fall Sinn macht ist eine ganz andere Sache, aber das ist zumindest ein wesentlicher Unterschied.

    Weitere Unterschiede fallen mir spontan nicht ein, auch wenn ich mich erinnere, dass meist die freie Funktion empfohlen wird (insbesondere wenn man keinen Zugriff auf private Member braucht und z.B. + wie auch schon von dir gezeigt mit += implementiert). Essenziell wichtig ist dann aber, dass der freie operator+ zusammen mit der Klasse bereit gestellt wird - im selben Header und im selben Namespace! Sonst gibt's früher oder später gemeine Fehler im Zusammenhang mit der Namensauflösung.

    Übrigens als Anmerkung - die Assertion scheint mir etwas zu hart. Es handelt sich ja in dem Falle nur um einen Fehler des Klienten und nicht um einen echten Programmierfehler innerhalb deiner Matrixklasse. Je nach Sinn und Zweck kann eine Exception sinnvoller sein - dazu gibt's aber sicherlich auch von 3 Experten 6 Meinungen. 😉



  • pospiech schrieb:

    oder wäre es besser letzeres so zu definieren:

    const matrix matrix::operator+(const matrix & rhs)
    {
    	matrix ans = *this;
    	ans+=rhs;
    	return ans;
    }
    

    Ist eigentlich wurscht. Fürs erste muss der Kopierkonstruktor implementiert sein, fürs zweite der Zuweisungsoperator (operator=).
    Sollte beides gleich schnell sein.
    Aber du wolltest das "+" doch außerhalb der Klasse implementieren?
    ala

    matrix operator+( const matrix& left, const matrix& right )
    {
      matrix m(left);
      return m += right;
    }
    

    Und dass der operator+ ein const-Objekt zurückgibt halte ich für ungeschickt. Kannst damit ja nachher nimmer ordentlich weitterrechnen.



  • franz schrieb:

    Ist eigentlich wurscht. Fürs erste muss der Kopierkonstruktor implementiert sein, fürs zweite der Zuweisungsoperator (operator=).

    6, setzen. 🙂 Beides läuft über den Copy-Constructor. (Direct vs. Copy Initialization)

    franz schrieb:

    Und dass der operator+ ein const-Objekt zurückgibt halte ich für ungeschickt. Kannst damit ja nachher nimmer ordentlich weitterrechnen.

    Das ist halt die leidliche und unendliche Diskussion. Wenn man allerdings der Maßgabe "do it as the ints do" folgt, dann muss der Rückkgabewert const sein, da fundamentale Typen an dieser Stelle rvalues sind.



  • pospiech schrieb:

    Da steht im wesentlichen das was ich als erstes gepostet habe, aber nicht warum 2tes besser oder schlechter wäre.

    Zudem ist mir der Vorteil der angegebenen Lösung über

    X X::plus(X const& rhs) const; //Implementiert die Addition
    X operator+(X const& lhs, X const& rhs)
    {
      return lhs.plus(rhs);
    }
    

    unklar.

    Wenn du nochmal genauer nachliest steht dort dass letzteres eventuell sinnvoll ist, wenn es erstens keinen operator += gibt und wenn man zweitens keine friend-deklaration haben möchte.
    Außerdem steht dort sehr wohl warum die freie Operatorfunktion besser ist:

    Um eine implizite Typumwandlung des ersten Operanden zu ermöglichen werden die binären arithmetischen Operatoren üblicherweise als freie Funktionen definiert.



  • franz schrieb:

    Und dass der operator+ ein const-Objekt zurückgibt halte ich für ungeschickt. Kannst damit ja nachher nimmer ordentlich weitterrechnen.

    Das ist halt die leidliche und unendliche Diskussion. Wenn man allerdings der Maßgabe "do it as the ints do" folgt, dann muss der Rückkgabewert const sein, da fundamentale Typen an dieser Stelle rvalues sind.[/quote]

    Ich habe mal gelesen dass man das machen soll damit folgendes nicht möglich ist

    a+b=c

    wenn man eigentlich

    (a+b)==c

    schreiben wollte (steht irgentwo in einem der Scott Meyers Bücher)



  • pumuckl schrieb:

    Wenn du nochmal genauer nachliest steht dort dass letzteres eventuell sinnvoll ist, wenn es erstens keinen operator += gibt und wenn man zweitens keine friend-deklaration haben möchte.

    Wenn ich nun den operator+= implementiere, dann brauche ich doch keine friend deklaration, oder? (deshalb meinte ich das man ein Beispiel mit friend angeben sollte, weil es sonst nicht jedem klar wird)

    pumuckl schrieb:

    Außerdem steht dort sehr wohl warum die freie Operatorfunktion besser ist:

    Um eine implizite Typumwandlung des ersten Operanden zu ermöglichen werden die binären arithmetischen Operatoren üblicherweise als freie Funktionen definiert.

    Jetzt verlassen mich mein C++ Kenntnisse. Wann wird ein Operand umgewandelt (Beispiel?) und warum ist das nur bei freien Funktionen möglich?



  • pospiech schrieb:

    Ich habe mal gelesen dass man das machen soll damit folgendes nicht möglich ist [...]

    Läuft auf genau das gleiche hinaus.

    5+6=7
    Anhand von ints kommt bei 5+6 ein rvalue raus. An diesen ist keine Zuweisung möglich.

    Wenn man eigene Operatoren baut, sollten sich diese Verhalten wie bei fundamentalen Typen. Deshalb sollte, wenn man dieser Maßgabe folgt, der Rückgabewert const sein.

    pospiech schrieb:

    Jetzt verlassen mich mein C++ Kenntnisse. Wann wird ein Operand umgewandelt (Beispiel?) und warum ist das nur bei freien Funktionen möglich?

    Das ist fast genau das, was ich oben auch schon erwähnt habe. Entweder wird der erste Operand in eine (für deinen Fall Matrix) umgewandelt, wenn sie einen entsprechenden Konstruktor anbietet - oder der Typ für die linke Seite wird durch den Operator diktiert. Warum das nur bei freien Funktionen möglich ist? 🙂 Weil lhs bei einer Memberfunktion bereit mit this belegt ist.



  • 7H3 N4C3R schrieb:

    pospiech schrieb:

    Ich habe mal gelesen dass man das machen soll damit folgendes nicht möglich ist [...]

    Läuft auf genau das gleiche hinaus.

    5+6=7
    Anhand von ints kommt bei 5+6 ein rvalue raus. An diesen ist keine Zuweisung möglich.

    Wenn man eigene Operatoren baut, sollten sich diese Verhalten wie bei fundamentalen Typen. Deshalb sollte, wenn man dieser Maßgabe folgt, der Rückgabewert const sein.

    Der Rückgabewert ist auch ohne const ein rvalue.



  • Zu const bei Rückgabetypen hat camper vor einiger Zeit etwas geschrieben: Hier im letzten Beitrag auf der Seite.

    dedwdcw schrieb:

    Der Rückgabewert ist auch ohne const ein rvalue.

    Nein, bei Klassentypen eben nicht.



  • Nexus schrieb:

    Zu const bei Rückgabetypen hat camper vor einiger Zeit etwas geschrieben: Hier im letzten Beitrag auf der Seite.

    dedwdcw schrieb:

    Der Rückgabewert ist auch ohne const ein rvalue.

    Nein, bei Klassentypen eben nicht.

    Was macht dieser Satz dann für einen Sinn?

    Aber so ein Movekonstruktor (oder Move-Zuweisung) ist natürlich nutztlos, wenn das Argument zwar ein Rvalue aber zusätzlich konstant ist, wie Meyers vorschlägt.



  • dedwdcw schrieb:

    Der Rückgabewert ist auch ohne const ein rvalue.

    Stimmt, da hast du Recht. Gerade selbst mal nachgelesen und auch mal wieder schlauer geworden. 🙂

    Dieser rvalue ist bei Objekten eines Klassentyps u.U. aber modifizierbar, was durch das const verhindert wird.

    Die Argumentation von Camper kannte ich auch noch nicht. Klingt auf jeden Fall interessant und nachdenkenswürdig.



  • Ich habe noch ein paar weitere Fragen. Ich bin gerade bei dem *= Operator.

    /*! Matrix Assignment Multiplication */
    matrix & matrix::operator*=(const matrix& rhs)
    {
    	// only if sizes are matched
    	assert(m_cols == rhs.m_rows);
    
    	matrix ans(rhs.m_rows, rhs.m_cols);
    	// y-axis 
    	for(int i=0;i<m_rows;++i)
    	{
    		// x-axis
    		for(int j=0;j<rhs.m_cols;++j)
    		{
    			ans(i,j)=0.0;
    			// k represents x and y position 
    			// of scalar multiplication
    			for(int k=0; k < m_cols; ++k)
    			{
    				ans(i,j) += m_Matrix[ArrPos(i,k)] * rhs(k,j);
    			}
    		}
    	}
    	*this = ans;
    	return *this;	
    }
    
    double matrix::operator()(int row, int col) const
    {
    	return m_Matrix[ArrPos(row, col)];
    }
    

    1. Ist der Compiler in der Lage zwischen dem opertor() und dem Konstruktor() zu unterscheiden, also zwischen

    double x = rhs(k,j);
    matrix ans(rhs.m_rows, rhs.m_cols);
    

    Beide Functionen sind ja die Klasse mit zwei Ints, nur unterschieden durch den Rückgabewert.

    2. Das ganze kompiliert nicht, weil ich die neue Matrix mit meiner Klasseninternen austauschen möchte. Ich hatte probiert das über
    *this = ans;
    zu machen, aber das geht wohl nicht. Außer das ich das ganze von Hand kopiere,
    gibt es noch eine andere Lösung?



  • pospiech schrieb:

    1. Ist der Compiler in der Lage zwischen dem opertor() und dem Konstruktor() zu unterscheiden.

    Ja, die beiden unterscheiden sich eindeutig durch den Aufruf.

    Ich hatte probiert das über
    *this = ans;
    zu machen, aber das geht wohl nicht.

    Definiere "geht nicht" - wenn du den op= richtig deklarierst sollte es eigentlich gehen. Was mir grade auffällt ist dass die Reihenzahl der temporären ans-Matrix eigentlich gleich this->m_rows sein sollte oder?
    Was bei der vorhandenen deklaration von operator() NICHT funktionieren wird ist die Zeile mit ans(i,j)=0.0; - du gibst ein temporäres Objekt zurück und weist dem einen Wert zu, statt ans wirklich zu verändern. [/quote]



  • pumuckl schrieb:

    Definiere "geht nicht" - wenn du den op= richtig deklarierst sollte es eigentlich gehen. Was mir grade auffällt ist dass die Reihenzahl der temporären ans-Matrix eigentlich gleich this->m_rows sein sollte oder?

    ups, ja ich habe den code entsprechend angepasst

    pumuckl schrieb:

    Was bei der vorhandenen deklaration von operator() NICHT funktionieren wird ist die Zeile mit ans(i,j)=0.0; - du gibst ein temporäres Objekt zurück und weist dem einen Wert zu, statt ans wirklich zu verändern.

    Wenn ich aber eine Referenz zurückgebe, dann sollte es funktionieren oder?

    Hier der geänderte Code:

    /*! Matrix Assignment Multiplication */
    matrix & matrix::operator*=(const matrix& rhs)
    {
    	// only if sizes are matched
    	assert(m_cols == rhs.m_rows);
    
    	matrix ans(m_rows, rhs.m_cols);
    	// y-axis 
    	for(int i=0;i<m_rows;++i)
    	{
    		// x-axis
    		for(int j=0;j<rhs.m_cols;++j)
    		{
    			ans(i,j)=0.0;
    			// k represents x and y position 
    			// of scalar multiplication
    			for(int k=0; k < m_cols; ++k)
    			{
    				ans(i,j) += m_Matrix[ArrPos(i,k)] * rhs(k,j);
    			}
    		}
    	}
    	// create Internal Matrix with new Size
    	if (m_cols != rhs.m_cols)
    	{
    		createMatrix(m_rows, rhs.m_cols);
    	}
    	// copy matrix to internal one
    	for(int i=0;i<m_rows;++i)
    	{
    		for(int j=0;j<rhs.m_cols;++j)
    		{
    			m_Matrix[ArrPos(i,j)] = ans(i,j);
    		}
    	}
    	return *this;	
    }
    


  • 7H3 N4C3R schrieb:

    Dieser rvalue ist bei Objekten eines Klassentyps u.U. aber modifizierbar, was durch das const verhindert wird.

    Hm, gut zu wissen. Ich bin von einer Umwandlung in ein LValue ausgegangen, aber das ist offensichtlich nicht möglich, sonst könnten zum Beispiel Non-Const-Referenzen auch auf Temporaries gebunden werden.

    pospiech schrieb:

    Wenn ich aber eine Referenz zurückgebe, dann sollte es funktionieren oder?

    Ja, dann musst du aber auch das const nach der Funktion wegnehmen.


Anmelden zum Antworten