Überladen von Operatoren



  • dein Operator / verändert in der Rechnung Vector a = v / 9.f; v und das darf nicht sein.

    greetz KN4CK3R



  • Aber wenn ich ne lokale kopie erzeuge, dann ist die hinterher nicht mehr gültig....
    Wenn ich nen Vektor in die Methode stecke, und ihn normalisieren möchte, dann will ich doch das v verändert wird...



  • Lybrial schrieb:

    Aber wenn ich ne lokale kopie erzeuge, dann ist die hinterher nicht mehr gültig....

    Natürlich ist sie gültig, wenn du sie zurückgibst. Schau dir nochmal den von mir geposteten Link an, wie man die Operatoren standardmäßig implementiert.

    Wenn ich nen Vektor in die Methode stecke, und ihn normalisieren möchte, dann will ich doch das v verändert wird...

    Beim Normalisieren kann man drüber streiten, aber bei c = a + b erwartet keiner, dass a verändert wird (ich wiederhole mich).



  • Michael E. schrieb:

    Lybrial schrieb:

    Aber wenn ich ne lokale kopie erzeuge, dann ist die hinterher nicht mehr gültig....

    Natürlich ist sie gültig, wenn du sie zurückgibst. Schau dir nochmal den von mir geposteten Link an, wie man die Operatoren standardmäßig implementiert.

    Wenn ich nen Vektor in die Methode stecke, und ihn normalisieren möchte, dann will ich doch das v verändert wird...

    Beim Normalisieren kann man drüber streiten, aber bei c = a + b erwartet keiner, dass a verändert wird (ich wiederhole mich).

    3.2 operator+, -, *, /, %

    Semantik: Addition, Subtraktion, Multiplikation, Division, Modulo. Es wird ein neues Objekt mit dem Ergebniszustand erzeugt. Die folgenden Ausführungen gelten jeweils analog für -, *, / und %.
    Übliche Deklaration:

    C/C++ Code:
    const X operator+(X const& lhs, X const& rhs);
    C/C++ Code:
    const X operator+(X const& lhs, X const& rhs);

    Übliche Implementierung:

    C/C++ Code:
    const X operator+(X const& lhs, X const& rhs)
    {
    /* Erzeugen eines neuen Objektes, dessen Attribute gezielt einzeln gesetzt werden. Oder: */
    X tmp(lhs); //Kopie des linken Operanden
    tmp += rhs; //Implementierung mittels des +=-Operators
    return tmp;
    }
    C/C++ Code:
    const X operator+(X const& lhs, X const& rhs)
    {
    /* Erzeugen eines neuen Objektes, dessen Attribute gezielt einzeln gesetzt werden. Oder: */
    X tmp(lhs); //Kopie des linken Operanden
    tmp += rhs; //Implementierung mittels des +=-Operators
    return tmp;
    }

    In vielen Fällen macht das Vorhandensein des operator+ auch die Existenz des operator+= sinnvoll, um die kürzere Schreibweise a += b an Stelle von a = a + b zu ermöglichen. Um das Verhalten beider Operatoren konsistent zu halten ist es daher üblich, operator+ im Sinne von operator+= zu implementieren wie gezeigt. Um eine implizite Typumwandlung des ersten Operanden zu ermöglichen werden die binären arithmetischen Operatoren üblicherweise als freie Funktionen definiert.
    Wird der Operator nicht mittels operator+= implementiert, benötigt er meist Zugriff auf private Attribute von X. In dem Fall wird er häufig als friend der Klasse deklariert oder greift auf eine öffentliche Methode der Klasse zu, die die eigentliche Operation ausführt:

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

    Das ist die betreffende Stelle, allerdings werden hier für operator + zwei Parameter reingesteckt, was garnicht möglich ist wenn man sie (wie es sauberes programmieren ist) außerhalb der Klasse definiert.



  • Lybrial schrieb:

    Das ist die betreffende Stelle, allerdings werden hier für operator + zwei Parameter reingesteckt, was garnicht möglich ist wenn man sie (wie es sauberes programmieren ist) außerhalb der Klasse definiert.

    Doch, als freie Funktion benötigt operator+ genau zwei Parameter.



  • Error: "must take either zero or one argument"

    Tritt im Header auf, dort ist die Überladung deklariert
    und im entsprechenden cpp File definiert!



  • Lybrial schrieb:

    Error: "must take either zero or one argument"

    Tritt im Header auf, dort ist die Überladung deklariert
    und im entsprechenden cpp File definiert!

    Glaub ich dir nicht. Hast du den Operator innerhalb einer Klasse deklariert?



  • Ja!

    class lCVector3 {					
    
    	public:
    		struct {
    			float x;
    			float y;
    			float z;
    		};							// coords
    
    		lCVector3();													// standart
    		lCVector3(const lCVector3 &v);									// copy
    		lCVector3(const float f);										// normal each element, same number
    		lCVector3(const float _x, const float _y, const float _z);		// normal each element, different number
    		lCVector3(const float *pfComponent);							// normal array with three elements
    
    		// operation assignments
    		lCVector3 operator = (const lCVector3 &v);
    		lCVector3 operator += (const lCVector3 &v);
    		lCVector3 operator -= (const lCVector3 &v);
    		lCVector3 operator *= (const lCVector3 &v);
    		lCVector3 operator *= (const float f);
    		lCVector3 operator /= (const lCVector3 &v);
    		lCVector3 operator /= (const float f);
    
    		// arithmetical operations
    		lCVector3 operator + (const lCVector3 &v);
    		lCVector3 operator - (const lCVector3 &v);
    		lCVector3 operator * (const lCVector3 &v);
    		lCVector3 operator * (const float f);
    		lCVector3 operator / (const lCVector3 &v);
    		lCVector3 operator / (const float f);
    
    		// compare operations
    		bool operator == (const lCVector3 &v);
    		bool operator != (const lCVector3 &v);
    
    		// function declaration
    		float		lCVector3Length(const lCVector3 &v);
    		float		lCVector3LengthSq(const lCVector3 &v);
    		lCVector3	lCVector3Normalize(const lCVector3 &v);
    		lCVector3	lCVector3NormalizeEx(const lCVector3 &v);
    		lCVector3	lCVector3Cross(const lCVector3 &v1, const lCVector3 &v2);
    		float		lCVector3Point(const lCVector3 &v1, const lCVector3 &v2);
    		float		lCVector3Angle(const lCVector3 &v1, const lCVector3 &v2);
    		lCVector3	lCVector3InterpolateCoords(const lCVector3 &v1, const lCVector3 &v2, const float p);
    		lCVector3	lCVector3InterpolateNormal(const lCVector3 &v1, const lCVector3 &v2, const float p);
    };
    


  • Dann zitier ich dich mal selbst:

    wenn man sie (wie es sauberes programmieren ist) außerhalb der Klasse definiert.

    Ansonsten haben dir auch schon genug Leute gesagt, dass der Operator als freie Funktion definiert werden soll und eben nicht in einer Klasse.



  • Man deklariert innerhalb der Klasse
    und definiert außerhalb.

    Ist doch normal.....

    Und genau so hab ichs gemacht,
    sehe dir den letzten Post von mir an, da hab ich
    mal den header gepostet, alles nur deklarationen
    nix definiert.



  • Lybrial schrieb:

    Man deklariert innerhalb der Klasse
    und definiert außerhalb.

    Nein, eine Funktion ist entweder innerhalb einer Klasse deklariert und definiert oder außerhalb. Beides gibts nicht. Du hast die Definition der Funktion einfach nur räumlich getrennt von der Klassendefinition, aber die Signatur der Funktionsdefinition sagt ganz klar, dass die Funktion zur Klasse gehört.

    class Foo
    {
        void bar();
    }
    
    void Foo::bar()    // bar gehört zu Foo
    {
    }
    
    void baz()    // freie Funktion
    {
    }
    


  • Versteh ich jetzt nicht ganz, funktioniert es dann grundsätzlich nicht
    wenn man das so macht oder ist das nur "unschön"

    Ein Beispiel das ich gemacht habe, eine Log-Klasse:

    Header

    #ifndef __LOGHANDLER
    #define __LOGHANDLER
    
    #include <stdio.h>
    #include "Singleton.hpp"
    
    enum LOGLEVEL {
        L_OK,
        L_ERROR,
        L_FATAL_ERROR,
        L_INFO
    	L_WARN
    	L_DEBUG
    };
    
    class CLogHandler : public TSingleton<CLogHandler> {
    
        public:
            CLogHandler();                                                                       //constructor
            ~CLogHandler();                                                                      //destructor
            void createLogHandler(const char *LogName);                                          //new logfile
            void message(const char *File, int Line, const char *Time, const char *msg, LOGLEVEL level); //writes message into logfile
    
        private:
            FILE *m_Logfile;                                                                     //the FILE ;)
            const char* convertEnum(LOGLEVEL level);                                             //to convert the enums ;)
    };
    
    #endif
    

    die cpp dazu, in der die Methoden definiert sind:

    #include <iostream>
    #include <string.h>
    #include "LogHandler.hpp"
    
    using namespace std;
    
    //constructor
    CLogHandler::CLogHandler(){};
    
    //destructor
    CLogHandler::~CLogHandler(){
        message(__FILE__, __LINE__, __TIME__,"End of logfile!", L_INFO);
        fclose(m_Logfile);
    }
    
    //create logfile and write head
    void CLogHandler::createLogHandler(const char *LogName) {
    
        try {
            m_Logfile = fopen(LogName, "w");//fopen = fileopen, w = write
    
            if (m_Logfile == NULL) {
                throw "Datei konnte nicht geoeffnet werden!";
            }
        } catch (char *ErrorString) {
            cout << "Fehler aufgetreten: " << ErrorString << endl;
            cout << "Programm wird beendet!" << endl;
            exit(0);
        }
    
        message(__FILE__, __LINE__, __TIME__, "THIS IS THE LOGFILE!!!", L_INFO);
    
        //print out actual build-configuration
        #ifdef _DEBUG
            message(__FILE__, __LINE__, __TIME__, "BUILD: DEBUG", L_INFO);
        #else
            message(__FILE__, __LINE__, __TIME__, "BUILD: RELEASE", L_INFO);
        #endif
    
        //close logfile and open it again with append
        fclose(m_Logfile);
        try {
            m_Logfile = fopen(LogName, "a");//fopen = fileopen, a = append
    
            if (m_Logfile == NULL) {
                throw "Datei konnte nicht geoeffnet werden!";
            }
        } catch (char *ErrorString) {
            cout << "Fehler aufgetreten: " << ErrorString << endl;
            cout << "Programm wird beendet!" << endl;
            exit(0);
        }
    }
    
    //message method to print a message into the logfile with LOGLEVEL
    void CLogHandler::message(const char *File, int Line, const char *Time, const char *msg, LOGLEVEL level) {
        fprintf(m_Logfile,"[<%s> - <%s>] at: <%20s>: <%d> <%s>\n", convertEnum(level), Time, File, Line, msg);
        fflush(m_Logfile);//forces instantly writing into the file
    }
    
    const char* CLogHandler::convertEnum(LOGLEVEL level) {
        switch(level) {
            case L_OK: return "INFO "; break;
            case L_ERROR: return "ERROR"; break;
            case L_FATAL_ERROR: return "FATAL"; break;
            case L_INFO: return "INFO "; break;
    		case L_WARN: return "WARN "; break;
    		case L_DEBUG: return "DEBUG"; break;
            default: return "INFO "; break;
        }
    }
    

    und das funktioniert herrlich



  • Mal noch ein paar andere Sachen:

    class lCVector3 {
    

    Schrecklicher Name. Was sollen l und C bedeuten?

    struct {
    			float x;
    			float y;
    			float z;
    		};							// coords
    

    Warum packst du das in ein struct? Warum benutzt du float statt double?

    l		lCVector3(const lCVector3 &v);									// copy
    

    Wofür musst du diesen Konstruktor explizit definieren? Danke an die Regel der großen Drei.

    lCVector3(const float f);										// normal each element, same number
    

    Was soll "normal" bedeuten? Komischer Satz

    lCVector3(const float *pfComponent);							// normal array with three elements
    

    Schlechter Stil. Übergib ein std::array, wenns denn unbedingt sein muss, Hauptsache du hast eine sinnvolle Größenüberprüfung.

    // operation assignments
    		lCVector3 operator = (const lCVector3 &v);
    

    Siehe Kommentar zum Copy-Konstruktor.

    lCVector3 operator *= (const lCVector3 &v);
    

    Wie ist die Multiplikation von zwei Vektoren definiert?

    lCVector3 operator /= (const lCVector3 &v);
    

    Wie ist die Division von zwei Vektoren definiert?

    // arithmetical operations
    		lCVector3 operator + (const lCVector3 &v);
    		lCVector3 operator - (const lCVector3 &v);
    		lCVector3 operator * (const lCVector3 &v);
    		lCVector3 operator * (const float f);
    		lCVector3 operator / (const lCVector3 &v);
    		lCVector3 operator / (const float f);
    
    		// compare operations
    		bool operator == (const lCVector3 &v);
    		bool operator != (const lCVector3 &v);
    

    Als freie Funktionen definieren.

    // function declaration
    		float		lCVector3Length(const lCVector3 &v);
    		float		lCVector3LengthSq(const lCVector3 &v);
    		lCVector3	lCVector3Normalize(const lCVector3 &v);
    		lCVector3	lCVector3NormalizeEx(const lCVector3 &v);
    

    Schlechte Namen, was soll lCVector3? Warum kürzt du die wesentlichen Teile ab? Ich weiß nicht, was du mit NormaliseEx meinst.

    lCVector3	lCVector3Cross(const lCVector3 &v1, const lCVector3 &v2);
    		float		lCVector3Point(const lCVector3 &v1, const lCVector3 &v2);
    		float		lCVector3Angle(const lCVector3 &v1, const lCVector3 &v2);
    

    Hier hast du wiederum drei Parameter, nämlich this, v1 und v2. Was ist das Kreuzprodukt dreier Vektoren? Point ist ein schlechter Name, den versteht man nur, weil Cross direkt drübersteht.

    lCVector3	lCVector3InterpolateCoords(const lCVector3 &v1, const lCVector3 &v2, const float p);
    		lCVector3	lCVector3InterpolateNormal(const lCVector3 &v1, const lCVector3 &v2, const float p);
    

    Hiermit dasselbe. Die Namen sind auch ziemlich nichtssagen.d



  • Lybrial schrieb:

    Versteh ich jetzt nicht ganz, funktioniert es dann grundsätzlich nicht
    wenn man das so macht oder ist das nur "unschön"

    Lies dir dazu bitte nochmal den entsprechenden Abschnitt in deinem Buch durch. Ich denke, wenn du dir meine Erklärung nochmal langsam durchliest, solltest du es auch so verstehen.

    Ein Beispiel das ich gemacht habe, eine Log-Klasse:

    Hier sind wieder so viele verbesserungswürdige Sachen drin... Wieso hast du eine Funktion createLogHandler, die die Aufgabe des Konstruktors übernimmt, während der Konstruktor gar nichts macht? Wieso erbst du von Singleton? Und so weiter, das geh ich nicht auch noch alles durch.



  • Danke für deine Tipps, sowas mag ich sehr gerne, mein ich ernst 🙂

    Aber zur Log-Klasse:

    Erbt von Singleton weil ich Singleton als Template Klasse schon implementiert hatte und meine Log-Klasse nur einmal exisitieren soll.



  • Lybrial schrieb:

    DErbt von Singleton weil ich Singleton als Template Klasse schon implementiert hatte und meine Log-Klasse nur einmal exisitieren soll.

    Und warum musst du im Code garantieren, dass nur eine Instanz erstellt werden kann? Deine Implementierung der Klasse passt übrigens gar nicht zum Singleton-Pattern.



  • Ok und warum nicht?
    So lange bin ich noch nicht in C++ um alles zu wissen aber über Tips freue ich mich. (Genau gesagt bin ich seid zwei Wochen an C++)

    wenn ich das ganze so mache um zurück zu Vektor-Klasse zu kommen funzts:

    Vector Vector::operator + (const Vector &v) {
    	Vector *v1 = new Vector(x + v.x, y + v.y, z + v.z);
    	return *v1;
    }
    


  • Lybrial schrieb:

    wenn ich das ganze so mache um zurück zu Vektor-Klasse zu kommen funzts:

    Vector Vector::operator + (const Vector &v) {
    	Vector *v1 = new Vector(x + v.x, y + v.y, z + v.z);
    	return *v1;
    }
    

    Und dies ist sehr schlechter Code (Ungeachtet davon das man weiterhin den operator+ nicht als Memberfunktion implementieren sollte). Wozu die Zeiger und das new?

    Warum nicht gleich so:

    Vector Vector::operator + (const Vector &v) {
        return Vector(x + v.x, y + v.y, z + v.z);
    }
    


  • Lybrial schrieb:

    Ok und warum nicht?

    Du stellst die falsche Frage. Du machst deinen Code durch das "Singleton" (Schau dir die Implementierung eines Singletons besser nochmal an. Danach vergisst du bitte seine Existenz und benutzt es nur noch in absoluten Ausnahmefällen.) komplizierter. Dafür musst du eine Rechtfertigung haben.



  • Ok, aber ist es nicht Rechtfertigung genug, das eine Instanz
    der Log-Klasse nur einmal existieren soll. Schließlich überschreibe
    ich meine Log-Datei jedes mal damit sie nicht eines Tages 20GB groß ist
    (ja es gibt commerzielle programme die das machen)
    Und wenn jetzt mehrere Teile eines Programms (Netzwerk, File-Handling,
    usw. )in die Log schreiben, will ich, dass sie alle in die selbe Log schreiben.


Anmelden zum Antworten