+ Operator überladen funktioniert nicht richtig



  • Gap schrieb:

    Wenn du mehrere aneinander hängen willst, musst du casten:

    string_cls temp = "Hallo, "
    temp += (string_cls)"Welt" + "!";
    

    Und um diesen Cast zu vermeiden implementiert man binäre Operatoren, die ein unabhängiges Objekt anhand ihrer Operanden erstellen normalerweise global und nicht als Methode.

    @Ynnus:
    1. Warum kann man kein string_cls-Objekt bei der Definition initialisieren?
    2. Warum erwarten deine Operatoren nur einen char* und nicht auch einen string_cls?
    3. Warum geben die Operatoren, die das aktuelle Objekt verändern keine Referenz auf this zurück?

    Gruß Caipi



  • Gap schrieb:

    Ich versuch jetzt mal den +operator richtig zu machen:

    string_cls string_cls::operator+(char* summand)
    {
        string_cls temp;
        temp = pointer;
        temp += summand;
        return temp;
    }
    

    Und warum machst du das dann nicht? :p

    Eine typische Implementation könnte so aussehen:

    string_cls operator +(const string_cls& lhs, const char* rhs)
    {
        string_cls tmp(lhs);
        tmp += rhs;
        return tmp;
    }
    

    Und für deinen Cast machen wir einfach nach eine zweite Funktion:

    string_cls operator +(const char* lhs, const string_cls& rhs)
    {
        string_cls tmp(lhs);
        tmp += rhs;
        return tmp;
    }
    


  • @groovemaster: Die Methode mit den mehreren Parametern von dir hab ich nicht so ganz verstanden. Der Compiler sagt mir dann immer, bei operatoren muss die FUnktion entweder 0 oder einen Parameter annehmen, 2 geht irgendwie nicht.

    Daher hab ich das jetzt mal versucht anders umzusetzen:

    string_cls string_cls::operator+(string_cls& summand)
    {
        string_cls temp;    //tempobjekt erstellen
        temp = *this;       //kopie von aufrufendem Objekt machen
        temp += summand;    //Summand zu temp hinzufügen
        return temp;        //temp zurückgeben
    }
    

    Damit ist es nun möglich, ein Objekt der Klasse mit anderen Objekten zu addieren.
    Etwa: object += object2 + object3 ...;
    Dabei verändern die Objekte 2 und 3 ihre Werte NICHT sondern werden nur addiert und geben die Summe als Rückgabewert raus, so wie es sein soll. 🙂

    Mit C-Strings will das Ganze nicht so recht. Man kann zwar jetzt mittels += einen C-String anhängen, aber nicht mit + mehrere C-Strings Verknüpfen wie oben jetzt mit den Objekten möglich ist.
    Trotzdem schonmal vielen Dank bis hier, das hilft mir schonmal weiter. Besonders der Tipp mit der Referenz, damit der Destruktor nicht immer aufgerufen wird. 😉

    @Ynnus:
    1. Warum kann man kein string_cls-Objekt bei der Definition initialisieren?
    2. Warum erwarten deine Operatoren nur einen char* und nicht auch einen string_cls?
    3. Warum geben die Operatoren, die das aktuelle Objekt verändern keine Referenz auf this zurück?

    Zu 1: Soweit war ich noch nicht bekommen. Das kann ich ja noch hinzufügen.
    Zu 2: Ist jetzt eingefügt, string_cls' Objekte kann man nun Verknüpfen
    Zu 3: Wenn ich das richtig verstehe meinst du das &-Zeichen welches Gap auch angesprochen hat? Das ist jetzt hinzugefügt.



  • Ynnus schrieb:

    @groovemaster: Die Methode mit den mehreren Parametern von dir hab ich nicht so ganz verstanden. Der Compiler sagt mir dann immer, bei operatoren muss die FUnktion entweder 0 oder einen Parameter annehmen, 2 geht irgendwie nicht.

    Du kannst einem binären Operator nur zwei Parameter übergeben wenn er global definiert ist. (Ansonsten wird für den linken Operanden automatisch this verwendet => Deshalb musst du nur einen Paramter angeben). Ich empfehle dir dringlichst nochmal das Thema Operatorenüberladung durchzulesen. (Und deine Fragen lösen sich in Luft auf :))

    Gruß Caipi



  • Ich hab noch eine Frage bezüglich der Überladung von Folgendem: operator char*( void ); wo ich dann eine Variabel als return zurückgebe anstelle das hier das Objekt als solches angesprochen wird.

    Also so sieht die Funktion aus:

    string_cls::operator char*( void )
    {
        return pointer;
    }
    

    Jetzt kann aber mal der Fall eintreten, dass ich an eine Funktion nicht den pointer sondern wirklich das Objekt übergeben möchte. Wenn ich dann aber folgendes schreibe:

    Funktionsaufruf(object);
    

    dann denkt sich der Compiler immer, dass ich auch hier den pointer des Objekts übergebe. Eben wegen der überladenen Funktion oben.
    Kann man da explizit sagen, dass es sich hierbei um das Objekt handelt und das nicht die überladene Funktion genommen wird?

    Oder muss ich mir da jetzt eine Funktion schreiben die (*this) zurückgibt?



  • Ynnus schrieb:

    Jetzt kann aber mal der Fall eintreten, dass ich an eine Funktion nicht den pointer sondern wirklich das Objekt übergeben möchte. Wenn ich dann aber folgendes schreibe:

    Funktionsaufruf(object);
    

    dann denkt sich der Compiler immer, dass ich auch hier den pointer des Objekts übergebe.

    Nein. Der Compiler ruft operator char* nur dann auf, wenn eine implizite oder explizite Umwandlung erforderlich ist. Sofern die Funktion aber ein Objekt deiner Klasse erwartet, ist dies nicht notwendig.

    btw:
    Dein operator char* ist alles andere als glücklich gewählt. Mal abgesehen davon, dass solche Operatoren zu unerwarteten Ergebnissen führen können (STL verwendet für die String Klasse zB eine extra Funktion), brichst du mit der Herausgabe des rohen Zeigers sämtliche Kapselung. Mach die Funktion zumindest const.



  • Mal abgesehen davon, dass solche Operatoren zu unerwarteten Ergebnissen führen können (...)

    In wie fern denn? Was genau könnte denn die Folge dabei sein bzw. wie tritt da denn ein unerwartetes Verhalten auf? (Speicherfehler und Programmabsturz oder sowas?)



  • Ynnus schrieb:

    Mal abgesehen davon, dass solche Operatoren zu unerwarteten Ergebnissen führen können (...)

    In wie fern denn? Was genau könnte denn die Folge dabei sein bzw. wie tritt da denn ein unerwartetes Verhalten auf? (Speicherfehler und Programmabsturz oder sowas?)

    Du kannst nicht kontrollieren, was das aufrufende Programm mit deinem Pufferspeicher anstellt - wenn da etwas "falsches" gemacht wird, bekommst du Speicherfehler (oder schlimmeres).

    string_cls str("Hallo");
    char* cstr=str;// hier greift der Umwandlungsoperator
    delete[] cstr;// der Puffer wird gelöscht - und spätestens beim str-Destruktor kracht es
    


  • Oha, ok, danke. Das wär' natürlich nicht so wunderbar. Was könnte man dagegen tun? Wenn ich im Dekonstruktor aber den Speicher des Pointers nur bedingt freigebe, wenn er denn tatsächlich auch noch existiert (!NULL ist), könnte man damit den Crash verhindern? Oder wird der pointer nicht NULL wenn ein anderer Pointer diese Speicherstelle freigibt? (es muss sich doch erkennen lassen, ob der Speicher noch besteht oder nicht...)



  • In dem Beispiel oben wird der member-Pointer nicht geändert (du arbeitest mit einer Kopie, also dürfte eine Überprüfung auf NULL nicht viel bringen. Und ein Pointer kann leider nicht überprüfen, ob jemand anderes den auf ihn verwiesenen Speicherplatz freigegeben hat.

    Am sichersten verhinderst du so einen Crash, indem du die Übergabe als char* komplett verhinderst (das mindeste ist, den Cast-Op in "operator const char*()" umzudefinieren, besser ist eine explizite Umwandlungsfunktion für diesen Zweck (ala string::c_str())).



  • Nein, das lässt sich leider nicht ohne weiteres erkennen. Wenn du einen Pointer auf einen Speicherbereich deletest, wird er nicht auf NULL gesetzt - und andere Pointer auf denselben Speicherbereich schon garnicht.



  • Ynnus schrieb:

    In wie fern denn? Was genau könnte denn die Folge dabei sein bzw. wie tritt da denn ein unerwartetes Verhalten auf? (Speicherfehler und Programmabsturz oder sowas?)

    Nein, ich meinte mit "unerwarteten Ergebnissen" eher, dass der Compiler zB dann eine solche implizite Umwandlung durchführt, wenn du das vielleicht gar nicht möchtest. Oder dass es zu nicht auflösbaren Mehrdeutigkeiten kommen kann. Das soll nicht heissen, dass du den Operator nicht verwenden sollst. Du musst aber noch mehr aufpassen, was du machst.

    Speicherfehler und Programmabsturz können entstehen, wenn jemand den Speicher hinter diesem rohen Zeiger manipuliert. Den Einwand von CStoll und dem delete[] vergisst du am besten schnell wieder, sowas kannst du nicht verhindern. Wer sowas macht, sollte einfach bestraft werden, imo. Wie gesagt, für den Anfang mach die Funktion erstmal const.

    string_cls::operator const char*() const
    

    Das hilft schon mal. Besser wäre es natürlich, wenn der zurückgegebene Zeiger auch noch const wäre, denn Lesen kann ja auch schon zu undefiniertem Verhalten führen. Das ist in der Praxis aber einfach zu unhandlich.
    Wenn jemand den String verändern will, dann biete op[] dafür an. Dort kannst du dann prüfen, ob der Anwender korrekt zugreift.



  • groovemaster schrieb:

    Den Einwand von CStoll und dem delete[] vergisst du am besten schnell wieder, sowas kannst du nicht verhindern. Wer sowas macht, sollte einfach bestraft werden, imo. Wie gesagt, für den Anfang mach die Funktion erstmal const.

    Das war auch nur ein Extrembeispiel. Und man kann solche Mainpulationen auf jeden Fall stark erschweren, indem man keine Zeiger (oder Referenzen) auf die lokalen Daten rausgibt - aber da mußt du abwägen, inwieweit es noch sinnvoll ist.



  • Wenn jemand den String verändern will, dann biete op[] dafür an. Dort kannst du dann prüfen, ob der Anwender korrekt zugreift.

    Wie überprüfe ich denn die korrekte Handhabung mit dem Pointer? Ich brauche den vor Allem auch, um ihn an diverse WinAPI-Funktionen als Buffer zu übergeben. Und da ist eben das Problem, dass diese Funktionen dort hinein schreiben und ich nicht sehe was genau die machen. Wie soll man da überprüfen, dass mit dem Pointer korrekt gehandhabt wurde?



  • Ynnus schrieb:

    Wie überprüfe ich denn die korrekte Handhabung mit dem Pointer? Ich brauche den vor Allem auch, um ihn an diverse WinAPI-Funktionen als Buffer zu übergeben. Und da ist eben das Problem, dass diese Funktionen dort hinein schreiben und ich nicht sehe was genau die machen. Wie soll man da überprüfen, dass mit dem Pointer korrekt gehandhabt wurde?

    So 'ne richtig zufrieden stellende Lösung hab ich dafür auch noch nicht gefunden. Ich mach es idR so, dass ich der Klasse eine resize() Funktion spendiere. Vor dem Aufruf der WinAPI Funktion, rufe ich dann resize() mit einer entsprechenden Grösse auf, um der Sequenz (in deinem Fall ein String) eine entsprechende Mindestlänge zu verpassen. Der WinAPI Funktion übergebe ich dann den Anfang des internen Puffers, zB &foo[0] oder &foo.front(), und die Länge (foo.length()), damit sie weiss, wieviel sie maximal reinschreiben darf. Nach dem Aufruf korrigiere ich die Länge wieder mit resize(), je nachdem, wieviel die WinAPI Funktion reingeschrieben hat.



  • Eine set_size(int size) Funktion hab ich auch schon drinne jetzt. Bei den WinAPI Funktionen muss man ja sowieso immer die Größe des Buffers angeben, demnach setze ich vorher die Größe, übergebe den pointer und teile die maximale Länge mit. Also so wie du das vorgeschlagen hast, im Grunde genommen.


Anmelden zum Antworten