Operatorenüberladung



  • Kann mir bitte einer mal die Operatorenüberladung erklären? Im meinem Buch wird sie zwar breitgetreten, aber ich verstehe gar nix mehr. Vor allem diese Sache mit dem const.... Naja und wäre nicht schlecht, wenn einer mal zu anfang den Sinn des ganzes Posten könnte und wie man sie geschickt einsetzt, wozu sie da ist und wozu nicht, eben das Konzept....



  • Kannst du mal genauer werden, was du schon weißt und was du nicht verstehst? Bevor ich jetzt lang und breit Operatorüberladung erkläre ... ich hab das Gefühl, du willst eigentlich wissen was const heißt. Vielleicht mußt du auch nur "Effektiv C++ Programmieren "lesen ...



  • nein was const heisst weiss ich ich habe das C/C++- Kompendium von Dirk Lois. Dort wird eine Klasse Vektor entwickelt, die den +-Operator überläd und somit vektoren addieren kann.

    class CVektor 
    {
        public:
            int getx ()
            { 
                return x;
            }
            int gety ()
            {
                return y;
            }
    
            int getz ()
            {
                return z;
            }
    
            CVektor (int x, int y, int z)
            {
                x = 7;
                y = 3;
                z = 6;
    
            }
    
            ~CVector (int x, int y, int z);
    
            CVektor operator+ (const CVektor &a) const
            {
                return Vector (x+a.getx (), y+a.gety (), z+a.getz ());
            }
        private:
            int x;
            int y;
            int z;
        protected:
    
    };
    

    meine Fragen sind erstens:

    1.) Was bringt es einen Operator zu überladen und warum macht man es?
    2.) Warum ist das Argument der Operatorenüberladung eine Referenz?
    3.) Kann man auch einen Zeiger nehmen?
    4.) Eine Referenz ist doch sowieso const wieso ist sie als const definiert?
    5.) was bedeutet das const hinter der Klammer?
    6.) geht das so mit jedem operator?
    7.) Wie greife ich auf das ürsprüngliche Operatorobjekt zu?!



  • 1.) Was bringt es einen Operator zu überladen und warum macht man es?

    Damit man folgendes schreiben kann:

    CVektor v(2, 3, 5), w(-15, 11, 1); // BTW: Dein Konstruktor und Destruktor sind Müll
    CVektor x = v + w;
    // anstatt vielleicht sowas: x = v.add(w); oder x = add(v, w)
    

    2.) Warum ist das Argument der Operatorenüberladung eine Referenz?

    Damit das Argument nicht kopiert wird. (-> Performance)

    3.) Kann man auch einen Zeiger nehmen?

    Theoretisch schon, aber dann müßte man x = &v + &w schreiben ...

    4.) Eine Referenz ist doch sowieso const wieso ist sie als const definiert?

    Nicht die Referenz ist const. Das ist eine Referenz-auf-const. Dh du kannst über diese Referenz nicht das dahinterliegene Objekt verändern.

    const bezieht sich immer auf den Teil der Deklaration, der genau links davon steht:

    int const * p; // Zeiger auf konstanten Integer
    int * const p; // kostanter Zeiger auf (veränderlichen) Integer
    const int * p; // Ausnahme: Genau das gleiche wie int const * p;
    int const& r; // Referenz auf konstanten Integer
    int & const r; // Schwachsinn :)
    

    5.) was bedeutet das const hinter der Klammer?

    Das die Methode das Objekt, mit dem sie aufgerufen wird, nicht verändern kann.

    b + a würde bei dir intern so gesehen: b.operator + (a);

    a ist eine const-Referenz, die Funktion kann also a nicht verändern. Damit sie b auch nicht verändert, muß sie const deklariert sein.

    6.) geht das so mit jedem operator?

    Denke schon.

    7.) Wie greife ich auf das ürsprüngliche Operatorobjekt zu?!

    Es gibt kein ursprüngliches Operatorobjekt.
    [/QB][/QUOTE]

    [ Dieser Beitrag wurde am 06.05.2003 um 13:58 Uhr von Bashar editiert. ]



  • achso also ich denke mit dem const ist jetzt klar vielen dank , zwei Fragen hätt ich allerdings noch

    1. Du sagtest damit b nicht verändert werden kann.... was ist denn b? Ist das b speziell auf dem +-Operator bezogen (also der zweite Summand) oder hat das einen tieferen Sinn? Wenn ich an andere Operatoren denke, z.B. den new-Operator oder die Streamoperatoren was wäre denn dann da das "b"? Oder funktioniert dort die Überladung doch anders? Wie könnte denn so ein anwendungsfall aussehen?

    2. zu dem ursrünglichen Objekt...der Operator hat doch vor der Überladung eine andere Funktion gehat

    wenn ich zwei Funktionen habe

    int uberladen (int i);
    int überladen (int i, int j);
    

    dann kann ich ja auf beide zugreifen, je nachdem welche Version ich wähle (hier wieviele Zahlen ich der Funktion übergebe). Wie sieht das denn nun analog zu den Operatoren aus?!



  • 1. Das hat nichts mit Operatorüberladung an sich zu tun, sondern mit normalen Methodenaufrufen:

    class Foo {
        int x;
      public:
        void bar();
        void baz() const;
    };
    
    Foo a;
    Foo const b;
    

    Im Aufruf a.bar(); kann es nun sein, dass a (dh konkret x) verändert wird. Bei a.baz() würde a jedoch auf keinen Fall verändert, da baz const deklariert ist. Daraus folgt, dass der Aufruf b.bar() nicht erlaubt ist, denn b ist const. b.baz() dagegen ist wieder kein Problem.

    1. Naja, du hast eben einen operator+ über 2 CVektor-Operanden definiert. Deshalb hört der + Operator für Zahlen oder Pointer nicht auf zu existieren, er wird auch nicht irgendwie verdeckt. Wenn du den operator+ nicht definiert hättest, wär es schlicht und einfach ein Fehler, zwei CVektoren zu addieren.


  • zu 2.) Es kann doch jetzt sein, dass ich nach der Addition zweier Vektoren wieder 2 Zahlen addieren will. Wo greife ich denn dann auf die "ursrüngliche Version" vom operator+ zu? oder muss ich den dann erneut überladen?!

    zu 1.) das ist klar für die Überladung des +-Operators. Jedoch haben andere Operatoren ja auch andere Aufgaben. Zum beispiel wird über dem new- Operator Speicher reserviert. was ist denn da das zweite element was nicht verändert werden darf?

    3.) Wie sieht denn ein konkreter Anwendungsfall aus?! Sagen wir mal nicht von +- operator, sondern mal ganz abstrakt vom *- operator oder vom -> operator oder meinetwegen auch vom new -operator.... was könnte ein selber Operator nach der Operatorüberladung ausführen wenn die Operatorüberladung sinnvoll sein soll? Wie setze ich dieses mittel ein?!

    Ach übrigens danke für deine schnelle und gute Hilfe!



    1. Wie ich schon sagte, die ursprüngliche Version verschwindet nicht.
    CVektor a, b, c;
    int x, y, z;
    
    c = a + b;
    z = x + y;
    

    beides funktioniert, weil der Compiler je nach Kontext (== Typen der Operanden) den richtigen Operator auswählt.

    1. Das was ich gesagt habe ist so direkt nur für binäre Operatoren anwendbar (dh +, -, *, /, %, &, |, ^, <<, >>, <, >, <=, >=, ==, !=, &&, ||). Unäre Operatoren (--, ++, *, &, -, (), [], ~, !, cast, ...) funktionieren ähnlich. Der ternäre ?: Operator kann nicht überladen werden. Für Zuweisung, new, delete und -> gibt es Sonderregeln. Und wahrscheinlich hab ich wieder die Hälfte vergessen.

    2. Du überlädst einen Operator in der Regel dann, wenn die Operation, die er ausführt, klarer durch einen Operator als durch eine Funktion ausgedrückt wird. ZB ist der + Operator sehr sinnvoll für alles was irgendwie als eine Form von Zahl durchgehen kann: Vektoren, Matrizen, komplexe Zahlen ... Der * Operator ist bei Vektoren zB schon wieder problematisch ... Skalarprodukt oder Kreuzprodukt?
      Wenn du eine Klasse hast, die funktioniert wie ein Pointer (Smart-Pointer) wär es wohl sinnvoll, die Operatoren -> und * zu überladen. Ein Array-artiger Container würde den [] Operator sinnvoll überladen (siehe std::vector oder auch std::map).



  • hehe danke echt lieb von dir

    in meinem Buch steht folgender Satz:

    Die Operatoren zur dynamischen Speicherverwaltung lassen sich ebenfalls überladen. Dies kann interessant sein, wenn man die Speicherallokation mit der Ausgabe von Debuginformationen verbinden will.

    wie würde das denn aussehen?!



  • Original erstellt von Parapiler:
    hehe danke echt lieb von dir
    in meinem Buch steht folgender Satz:
    Die Operatoren zur dynamischen Speicherverwaltung lassen sich ebenfalls überladen. Dies kann interessant sein, wenn man die Speicherallokation mit der Ausgabe von Debuginformationen verbinden will.
    wie würde das denn aussehen?!

    lustige idee

    void* operator new(size_t s)
    {
       void* p=malloc(s);
       ofstream("memlog.txt",ios::app)>>"new "<<s<<" returns "<<p<<endl;
       return p;
    }
    void operator delete(void* p)
    {
       ofstream("memlog.txt",ios::app)>>"delete "<<p<<endl;
       free(p);
    }
    

    und dann noch ein programm basteln, das das logfile auswertet, ob da ein speicher loch ist oder so.

    ich habe natürlich nicht daran gedacht, zu prüfen, ob ofstream("memlog.txt",ios::app)>>"new "<<s<<" returns "<<p<<endl; voeleicht new aufruft. das gäbe einen interessanten effekt.



  • sorry, ich habe von dem ganzen natürlich wieder mal keinen plan kannst das vielleicht mal erläutern? was willst du prüfen? muss man das was du prüfen willst immer prüfen?!



  • ich habe natürlich nicht daran gedacht, zu prüfen, ob ofstream("memlog.txt",ios::app)>>"new "<<s<<" returns "<<p<<endl; voeleicht new aufruft

    Es darf zumindest new aufrufen. Demzufolge wäre man mit den C-File-Funktionen wohl besser dran.
    Auf der anderen Seite:
    Warum den globalen operator new überladen?
    Prinzipiell sollte es doch auf Klassenebene reichen (optional durch eine mixin-Klasse) und zum Anderen bringen viele Implementationen doch sowieso ein ähnliches (Debug)-new mit.

    PS: Für alle die es nicht gesehen haben sollten, Volkard hat in seinem Beispiel zweimal ausversehen >> statt << geschrieben.



  • wie verbinde ich denn nun die Debuginformationen mit der Speicherreservierung!?



  • und was hat der ofstream da zu suchen?! warum nutzt man den?!



  • Original erstellt von Parapiler:
    sorry, ich habe von dem ganzen natürlich wieder mal keinen plan kannst das vielleicht mal erläutern? was willst du prüfen? muss man das was du prüfen willst immer prüfen?!

    man vergißt gerne mal, ein delete aufzurufen. oder manchmal ruft man eins zu viel auf. mit desem überladenen operator new/delete könnte man sich alle (nicht ganz alle) aufrufe von new/delete mitloggen lassen, und wenn man den verdacht hat. daß was nicht stimmt, das logfile auswerten.
    nur mal so als beispiel, daß du was konkretes in der hand hast auf deine frage "wie würde das denn aussehen?!". man muß das aber nicht so prüfen. da gibts viel bessere sachen.



  • also mal ganz konkret: in einem Programm von mir wird eine Exception ausgelöst und niemand findet einen Fehler, ich dachte, eventuell kann man mittels Exceptionhandling und Operatorüberladung von new den Speicher kontrollieren und so leichter den Fehler finden. Der Fehlerhafte Code ist dieser hier:

    int bytecounter = 0;
            char* recbyte =  NULL;
            unsigned long* bytes = new unsigned long [];
            *bytes = 0;
    
            //warten, bis daten am Socket anliegen
            while (*bytes == 0)
            {
    
                //Anzahl der wartendem Bytes bestimmen und Speicher reservieren
                rc = ioctlsocket (daMember[counter].Socket, FIONREAD, bytes);
    
                if (rc != 0)
                {
                    int n = WSAGetLastError ();
    
                    switch (n)
                    {
                        case WSANOTINITIALISED:
                            cout << "Fehler: Socket ist nicht initialisiert" << endl;
                            return SOCKET_ERROR;
                        case WSAENETDOWN:
                            cout << "Fehler: Das Netzwerksystem hat versagt" << endl;
                            return SOCKET_ERROR;
                        case WSAEINPROGRESS:
                            cout << "Fehler: Ein blockierender Prozess ist im Vorgang" << endl;
                            return SOCKET_ERROR;
                        case WSAENOTSOCK:
                            cout << "Fehler: Der Descriptor ist kein Socket." << endl;
                            return SOCKET_ERROR;
                        case WSAEFAULT:
                            cout << "Fehler: Der Pointer zeigt auf einen ungültigen Adressenbereich" << endl;
                            return SOCKET_ERROR;
    
                    }
    
                }
    
                if (*bytes != 0)
                {
    
                    cout << "Daten liegen vor" << endl;
                    break;
                }
            }
    
            bytecounter = *bytes;
            recbyte = new char [bytecounter];
    
            //Struktur auslesen
            int n = recv (daMember[counter].Socket, recbyte, bytecounter, 0);
    
            if (n == 0)
            {
                cout << "Empfangen fehlgeschlagen" << endl;
                return SOCKET_ERROR;
    
            }
    
            if (n == -1)
            {
                cout << "Empfangen fehlgeschlagen" << endl;
                return SOCKET_ERROR;
            }
    
            cout << recbyte << endl;
    
            //UI- Struktur füllen
            memset (&userinformation, 0, sizeof (UI));
    
            //erstes Byte (BOOLEAN) des Empfangsbuffers in registry kopieren
            memcpy (&userinformation[counter].registry, recbyte + sizeof (int) , sizeof (bool));
    
            //für den Fall eines Gastzuganges
            if (userinformation[counter].registry == false)
            {
    
                //Speicher für den char* reservieren (Nicknamen)
                userinformation[counter].nickname = new char [100];
                //Nicknamen rauskopieren
                memcpy (&userinformation[counter].nickname, recbyte + sizeof (int) + sizeof (bool), bytecounter- sizeof(bool)-sizeof (int));
                //userinformation[counter].nickname = "hallo";
                cout << &userinformation[counter].nickname << endl;
                cout << userinformation[counter].nickname << endl;
                daMember[counter].myuser = new char [bytecounter];
                memcpy (&daMember[counter].myuser, userinformation[counter].nickname, bytecounter - sizeof (bool));
    
            }
    


  • unsigned long* bytes = new unsigned long [];

    bereits hier kann ich nicht folgen.



  • naja dort wird eben nur für einen long Speicher reserviert, ist doch ganz normal:

    ausführlich

    unsigned long bytes* = NULL;
    bytes = new unsigned long [1];



  • Original erstellt von Parapiler:
    also mal ganz konkret: in einem Programm von mir wird eine Exception ausgelöst und niemand findet einen Fehler

    welche exception?



  • beim zweiten cout und dem letzten memcpy wird eine Access violation ausgelöst (letzte und drittletzte zeile) und suche nun nach Hinweisen. Allerdings finde ich keine. Der Speicher ist reserviert, die pointer sind gültig.... ich habe da keine ahnung mehr.... ich dachte eventuell könnte man so ein Abbild von Speicher usw. bekommen und so auf einen Hinweis stoßen, schrittweises durchgehen des Codes ist ja gescheitert (nicht nur bei mir sondern auch bei anderen die ich mal nachgucken haben lasse, niemand hat was gefunden).

    Eventuell etwas zur Vorgeschichte:

    Der Code empfängt einen Buffer von einem Socket. Über das Socket wird eine in einem Buffer serialisierte (mit memcpy hintereinanderkopierte) Struktur. Diese Struktur soll nun wieder entfriemelt werden.


Log in to reply