Kruze Frage zur Ermittlung der String-Länge



  • was ich nicht verstehe ist folgende zeile

    delete[] kontoinhaber;
    

    "kontoinhaber" ist doch ein "const char*", kann also nicht verändert werden, aber hier wird bewusst der speicherbereich frei gegeben. Müsste es da nicht bei der Übersetzung einen Fehler geben?

    Edit: doch jetzt sehe ich, dass es doch nur ein fehler war, in der Musterlösung sieht es besser aus.



  • hustbaer schrieb:

    Die "Musterlösung" ist auch nicht exception-safe.
    Besser wäre so:

    void Konto::set_kontoinhaber(const char* kontoinhaber) 
    { 
        // Ermittlung der Stringlänge 
        int i=0; 
        while(kontoinhaber[i++]!='\0'); 
        // Speicherfreigabe und -anforderung 
        char* temp = new char[i];
        delete[] kontoinhaber; 
        kontoinhaber = temp; 
        // Zeichenweises Kopieren 
        for(int j=0; j<i; j++) 
            kontoinhaber[j]=kontoinhaber[j]; 
    }
    

    Ich glaube die this - Zeiger hatten schon ihre Berechtigung, es scheint, als gäbe es in der Klasse Konto auch eine "kontoinhaber" als Member. Nun ja, eine bessere Benennung der Variablen wäre schon nicht schlecht gewesen^^.



  • wozu gibs denn this? finds schon ok, für getter/setter parameter dieselben namen zu verwenden, wie für die entsprechenden member.

    und die änderung von hustbaer trägt auch nix zur exception safety bei. das war der code schon vorher, da es schlicht nix gibt, was man in diesem minicode in hinsicht darauf beachten müsste.



  • thordk schrieb:

    wozu gibs denn this? finds schon ok, für getter/setter parameter dieselben namen zu verwenden, wie für die entsprechenden member.

    Ok, das mit den Variablen-Namen habe ich übersehen.
    Davon abgesehen finde ich es grausig this-> zu schreiben. "this" gibts für Fälle wo man den "this" Zeiger irgendwo übergeben muss, also für Dinge wie "ptr->Foo(this)". IMO sicher NICHT dafür dass man zwischen Membern und nicht Membern mit gleichem Namen unterscheiden kann. BTW: wenn man "m_" für Member verwendet hat man das Problem auch gleich garnicht.

    thordk schrieb:

    und die änderung von hustbaer trägt auch nix zur exception safety bei. das war der code schon vorher, da es schlicht nix gibt, was man in diesem minicode in hinsicht darauf beachten müsste.

    Blödsinn. Original:

    void Konto::set_kontoinhaber(const char* kontoinhaber)
    {
        // Ermittlung der Stringlänge
        int i=0;
        while(kontoinhaber[i++]!='\0');
        // Speicherfreigabe und -anforderung
        delete[] this->kontoinhaber;           // alten Speicher freigeben, Zeiger zeigt aber noch auf den freigegebenen Speicher
        this->kontoinhaber = new char[i];      // hier fliegt ein bad_alloc - *bumm* du bist tot
        // Zeichenweises Kopieren
        for(int j=0; j<i; j++)
        {
            this->kontoinhaber[j]=kontoinhaber[j];
        }
    }
    

    Die "richtige" Version sieht dann so aus (diesmal mit passenden Variablen Namen):

    void Konto::set_kontoinhaber(const char* name) 
    { 
        // Ermittlung der Stringlänge 
        int i=0; 
        while(name[i++]!='\0'); 
        // Speicherfreigabe und -anforderung 
        char* temp = new char[i];              // neuen Speicher anfordern, hier kann ein bad_alloc fliegen, aber es wurde noch nichts verändert
        delete[] kontoinhaber;                 // alten Speicher freigeben, kann nix werfen
        kontoinhaber = temp;                   // variable umsetzen, kann auch nix werfen
        // Zeichenweises Kopieren 
        for(int j=0; j<i; j++) 
            kontoinhaber[j]=name[j];           // kopierschleife kann auch nix werfen
    }
    


  • Also ich find das Arbeiten über this viel sinnvoller. Sich so unpraktische Kürzel wie m_x zu entwickeln ist nicht wirklich praxisnah.

    Auch ist es viel leichter Fremdcode später zu lesen, da es nicht durch eine Konvention festgelegt ist, sondern tatsächlich korrekte Syntax ist, die keine andere Variante zulässt.

    Und den Parameter anders zu nennen als die Variable, verschlechtert meiner Ansicht nach auch die Lesbarkeit und fördert damit nicht dem Verständnis des Codes. Das einzige Argument, welches gegen eine Verwendung von this->x anstatt m_x spricht, ist, dass man - insbesodnere als Anfänger - vergisst, this-> zu schreiben und so seine Operationen auf der falschen Variable ausführt. Aber wenn man schon etwas länger programmiert, passiert einem sowas eigentlich nicht.

    Ich bin übrigens auch kein Fan von getX und setX(Y) sondern benutze lieber X() und X(y), durch das überladen ist das ja kein Problem und es bleibt genauso eindeutig wie mit dem set/get Präfix.



  • void Konto::set_kontoinhaber(const char* name)
    {
        std::size_t len = 0; // std::size_t ist positive Ganzzahl, da Größe. Fall nicht erlaubt: unsigned int
        while(name[len++]);
    
        delete [] m_kontoinhaber; 
        m_kontoinhaber = new char[i];
    
        for (std::size_t pos = 0; pos < len; ++pos)
            m_kontoinhaber[pos] = name[pos];
    }
    

    ... so ist doch netter ...



  • @(D)Evil: und wieder nicht exception safe. Und es muss "new char[len];" heissen, "i" ist nicht definiert.

    @all: ist euch allen das (exception safety) eigentlich *so* egal, oder seht/glaubt/versteht ihr es nur nicht?

    @noNeed 4 aNick: es IST praxisnah, wie man an hunderten und tausenden von (kommerziellen, hobby-, ...) Source-Codes sieht die m_x verwenden. Dass du es nicht magst ist deine Sache, bloss mach dich bitte nicht lächerlich indem du behauptest es wäre nicht praxisnah.



  • hustbaer schrieb:

    Davon abgesehen finde ich es grausig this-> zu schreiben. "this" gibts für Fälle wo man den "this" Zeiger irgendwo übergeben muss, also für Dinge wie "ptr->Foo(this)". IMO sicher NICHT dafür dass man zwischen Membern und nicht Membern mit gleichem Namen unterscheiden kann. BTW: wenn man "m_" für Member verwendet hat man das Problem auch gleich garnicht.

    In meinem Oberstübchen rumpelt was bezüglich this. Ich meine mich erinnern zu können, dass es bei Verwendung von templates angeraten sein kann, this->blabla zu benutzen. Hatte meine ich was mit Vererbung und name-lookup zu tun, ich suchs mal raus...

    /edit:
    Aalso. Bei abhängigen Basisklassen á la

    template <typename T>
    class D : public B<T>
    {  
    };
    

    ist this-> nötig, um
    a) virtuelle Funktionen der abhängigen Basisklasse aufzurufen (ein B<T>::foo() würde virtualität verhindern)
    b) Den lookup zu verzögern, wenn es z.B. eine Spezialisierung der Basisklasse vorhanden ist.
    Ist im aktuellen Problem zwar nicht anwendbar, aber ganz ohne this-> kommt man nicht aus 😉



  • Yo ich weiss.
    Templates, besonders mit dependent base classes, sind eine ganz eigene Sache.
    Ich denke es war klar was ich meinte, nämlich die verwendung von "this->" in "normalem" Code -- der eben ohne das auskommt, solange man nicht gerade Parameter/lokale Variablen gleich nennt wie Member.
    Was IMO komplett pöse weil verwirrend ist.



  • Hi,

    ich empfinde die Verwendung von "this->" (mal abgesehen von der meistens unnötigen Tipparbeit) eine Einschränkung der Generizität ... ebenso wie die direkte Qualifizierung mittels std:: - und lasse Beides deswegen (und die "m_"-Notation ebenfalls). 😃

    Gruß,

    Simon2.



  • Simon2 schrieb:

    ich empfinde die Verwendung von "this->" (mal abgesehen von der meistens unnötigen Tipparbeit) eine Einschränkung der Generizität ... ebenso wie die direkte Qualifizierung mittels std:: - und lasse Beides deswegen (und die "m_"-Notation ebenfalls). 😃

    Was hat den this und std:: mit Generizität zu tun?



  • Simon2 schrieb:

    Hi,

    ich empfinde die Verwendung von "this->" ... eine Einschränkung der Generizität ...

    Es ist genau umgekehrt, da manche Konstrukte eben nur dann funktionieren, wenn man "this->" benutzt. Ergo man muß nur etwas mehr tippen verliert aber nichts.

    Simon2 schrieb:

    ebenso wie die direkte Qualifizierung mittels std::

    Der unqualifizierte Zugriff auf den Namesraum std via using Direktive führt doch Namensräume ad absurdum. Gerade C++ Projekte sind meist keine Ex und Hopp Projekte, so daß es sehr sinnvoll ist die Lesbarkeit des Programmcodes sicherzustellen. std::vector ist nun einmal sehr viel informativer als vector. Letzteres kann irgend ein vector aus irgend einem Namensraum sein.



  • Runde 126 schrieb:

    Simon2 schrieb:

    ich empfinde die Verwendung von "this->" (mal abgesehen von der meistens unnötigen Tipparbeit) eine Einschränkung der Generizität ... ebenso wie die direkte Qualifizierung mittels std:: - und lasse Beides deswegen (und die "m_"-Notation ebenfalls). 😃

    Was hat den this und std:: mit Generizität zu tun?

    Ma legt über den Namen hinaus bereits einen "Gültigkeitsbereich" (meint nicht C++-Scope - finde gerade keine bessere Bezeichnung) fest.
    Was, wenn in einer späteren Version nicht mehr std::cout, sondern das semantisch identische myOwn::cout verwendet werden soll ?
    Was, wenn eine Membervariable später "ausgelagert" wird (z.B. in den globalen Namensraum) ?

    => Jeweils an 1000 Codestellen rumändern, statt da, wo es hingehört: In der Deklaration:

    // Version 1:
    
    // A.h 
    struct A {
       int x, y;
       A();
       void f() const;
       void g();
    };
    
    // A.cpp
    #include <iostream>
    using std::cout;
    
    A::A() : x(0), y(0) {}
    void A::f() const { cout << x; }
    void A::g() { f(); ++y; }
    

    Zwischenzeitlich haben sich die Anforderungen ein wenig geändert, so dass f() und x eigentlich nicht mehr viel mit A zu tun haben.

    // Version 2:
    
    // A.h 
    struct A {
       int y;
       void g();
    };
    
    // A.cpp
    #include <MyTools>
    using myTools::cout;
    
    static int x = 0;
    void f() {   cout << x; } // Code identisch mit oben
    
    void A::g() { f(); ++y; } // Code identisch
    

    ... später alles ausgelagert:

    // Version 2:
    
    // A.h 
    struct A {
       int y;
       void g();
    };
    
    // A.cpp
    #include <MyTools>
    using myTools::f;
    
    void A::g() { f(); ++y; } // Code identisch
    

    Das wäre mit this->x und this->f() mehr Getippe (mit mehr Fehlerpotential) geworden.

    Gruß,

    Simon2.



  • ~john schrieb:

    Simon2 schrieb:

    Hi,

    ich empfinde die Verwendung von "this->" ... eine Einschränkung der Generizität ...

    Es ist genau umgekehrt, da manche Konstrukte eben nur dann funktionieren, wenn man "this->" benutzt....

    Du beziehst Dich hier auf einen Spezialfall im template-name-lookup. In allen anderen Fällen verbaut man sich aber gerade die Anbindung an templates durch die Forderung von "this->"....
    D.h. ich habe die Wahl, ob von 100 Idiomen 99 oder 1 funktionieren. Welcher Ansatz generischer ist, kann man IMHO deutlich ablesen.

    ~john schrieb:

    ...
    Der unqualifizierte Zugriff auf den Namesraum std via using Direktive führt doch Namensräume ad absurdum...

    Wieso sollte das der Fall sein ?
    Im Gegenteil: Dass ich an einer zentralen Stelle "umswitchen" kann, stärkt die Bedeutung von namespaces.

    ~john schrieb:

    ...Letzteres kann irgend ein vector aus irgend einem Namensraum sein.

    Eben: Das nennt man Generische Programmierung !
    Muss man nicht mögen oder einsetzen, ich halte es aber (gerade in größeren Projekten) für einen großen Vorteil.
    Mit demselben Argument müsste man sonst auch overloading bei Funktionen wieder abschaffen, weil "... man ja gar nicht mehr sieht, welche Funktion aufgerufen wird...".

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Was, wenn in einer späteren Version nicht mehr std::cout, sondern das semantisch identische myOwn::cout verwendet werden soll ?

    Das wäre im Extremfall eine automatische Suchen&Ersetzen Aktion - kein Drama, da ja der Name "std::cout" eindeutig ist. Was bei "cout" nicht gewährleistet ist. Wenn man den Ausgabestrom verändern können will, so sollte man das auch im Design berücksichtigen. Referenzen von std::cout oder anderen std::ostream lassen sich leicht erzeugen. Man sollte im Design immer darauf vorbereitet sein, daß man den Ausgabestrom umlenken kann,w enn man einen fest kodierten Bezug auf eine globale Variable nimmt (std::cout), ist das eindeutig nicht der Fall, und dann muß man mit den Defiziten leben.



  • ~john schrieb:

    ...
    Das wäre im Extremfall eine automatische Suchen&Ersetzen Aktion - kein Drama...

    Aber warum sollte ich das tun, wenn ich einfach nur ein einziges "using" umbiegen müsste ... was auch im vi prima funktioniert ? 😕

    BTW: Du solltest Dich nicht zu sehr am cout-Beispiel aufhängen. Das gilt genauso bei allen anderen Konstrukten.

    Nochmal: Mit derselben Argumentation musst Du auch gegen overloading bei Funktionen sein, weil man da ja auch:

    • vollqualifizierte Namen hat (f_int(int), f_char(char), ...),
    • bei denen man sich keine "Sprachregel für die Namensauflösung" merken muss und
    • die man ganz easy per Suchen&Ersetzen ändern kann.

    Wie gesagt: Die Frage war hier nicht "Warum man (nicht) generisch programmieren soll ?", sondern: "Warum schränkt die Verwendung von this->/std:: die Generizität ein ?".
    Und die beantwortest Du ganz eindeutig und richtig mit Deinem Wunsch nach "festen Bezügen".

    Gruß,

    Simon2.



  • Simon2 schrieb:

    In allen anderen Fällen verbaut man sich aber gerade die Anbindung an templates durch die Forderung von "this->"....

    Beispiel?

    Wieso sollte das der Fall sein ?
    Im Gegenteil: Dass ich an einer zentralen Stelle "umswitchen" kann, stärkt die Bedeutung von namespaces.

    Da beliebige includes (man denke nur mal an die vielen C-Libraries) Kollisionen auslösen können, ist dies nicht sonderlich geschickt. Man kann ja Umbennungen von Namesräumen durchführen, so daß man im Projekt seine eigenen Benennung für einen Namensraum hat. Das erfüllt Deine Vorgabe, und man müllt den globalen Namenraum trotzdem nicht zu.

    Eben: Das nennt man Generische Programmierung !

    Diese Interpretation von generischer Programmierung kann ich nicht teilen. Typsicherheit ist zum Beispiel in Ada ein fundamentaler Bestandteil von generischer Programmierung, die C++ leider nicht im Kontext von Templates kennt. Man kann Templates leider beliebige Typen übergeben, so daß die von allen "geliebten" und sehr "aussagekräftigen" Fehlermeldungen auftreten. Man muß das Problem nicht vorsätzlich vergrößern, wenn es Möglichkeiten gibt es zu umgehen in dem man das Programm anders entwirft.

    Mit demselben Argument müsste man sonst auch overloading bei Funktionen wieder abschaffen, weil "... man ja gar nicht mehr sieht, welche Funktion aufgerufen wird...".

    Der Vergleich hinkt, da es sich um eine andere Problematik handelt.

    Grüße



  • ~john schrieb:

    Simon2 schrieb:

    In allen anderen Fällen verbaut man sich aber gerade die Anbindung an templates durch die Forderung von "this->"....

    Beispiel?...

    Habe ich oben schon gebracht. In Kurzform: mit "this->" nagele ich die Entscheidung fest, dass sich das gesuchte Element in meinem Vererbungsbaum befinden muss. Ohne kann ich ein passendes Element auch außerhalb finden => generischer.

    ~john schrieb:

    Wieso sollte das der Fall sein ?
    Im Gegenteil: Dass ich an einer zentralen Stelle "umswitchen" kann, stärkt die Bedeutung von namespaces.

    Da beliebige includes (man denke nur mal an die vielen C-Libraries) Kollisionen auslösen können, ist dies nicht sonderlich geschickt. ...

    😕 Was hat das denn damit zu tun ?
    Gerade durch die namespaces werden doch Kollisionsmöglichkeiten aufgehoben (und zwar durch ein "using std::x;" exakt so wie ein permanentes "std::x..."

    ~john schrieb:

    Man kann ja Umbennungen von Namesräumen durchführen, so daß man im Projekt seine eigenen Benennung für einen Namensraum hat. Das erfüllt Deine Vorgabe, und man müllt den globalen Namenraum trotzdem nicht zu.

    Das versehe ich jetzt gar nicht.
    Ich habe auch nirgendwo von einer "Zumüllung des globalen namespaces" gesprochen und sehe auch keine....

    ~john schrieb:

    Typsicherheit ist zum Beispiel in Ada ein fundamentaler Bestandteil von generischer Programmierung, die C++ leider nicht im Kontext von Templates kennt. Man kann Templates leider beliebige Typen übergeben, so daß die von allen "geliebten" und sehr "aussagekräftigen" Fehlermeldungen auftreten. Man muß das Problem nicht vorsätzlich vergrößern, wenn es Möglichkeiten gibt es zu umgehen in dem man das Programm anders entwirft....

    1.) templates sind absolut "typsicher" - genauso typsicher wie "nicht-template-Code", weil der Compiler letztlich "instantiierten Code" mit konkreten Typen sieht. Welche "Typsicherheit" und welche generische Programmierung ADA anbietet, weiß ich nicht, weil ich diese Sprache nicht beherrsche.
    2.) Hier sprichst Du Dich halt gegen generische Programmierung wie sie C++ anbietet aus, weil sie Dir zu komplex erscheint. Macht ja nichts, aber es bleibt dabei: this->/std:: schränken die Generizität ein ! Du empfindest das als Vorteil, ich als Nachteil - damit ist doch die eigentliche Frage beantwortet.

    ~john schrieb:

    Mit demselben Argument müsste man sonst auch overloading bei Funktionen wieder abschaffen, weil "... man ja gar nicht mehr sieht, welche Funktion aufgerufen wird...".

    Der Vergleich hinkt, da es sich um eine andere Problematik handelt.

    Keineswegs !
    In allen von Dir genannten Aspekten verhält sich overloading genau so wie templates => Alle Deine Argumente treffen ebenso auf overloading zu.
    => Du kannst nicht konsistent für das eine und gegen das andere sein.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Und die beantwortest Du ganz eindeutig und richtig mit Deinem Wunsch nach "festen Bezügen".

    Es geht hier um Zusicherung von Eigenschaften von Objekten. Wenn ich in einem Programmteil "std::cout" (nur um bei diesem Beispiel zu bleiben) verwende, dann ist nur garantiert, daß der Code damit funktioniert. Wenn ich nun das einfach durch etwas anderes ersetze ist es dann wirklich garantiert, daß das ganze auch noch funktioniert wie vorgesehen? Weshalb wurde dann das Design nicht so gewählt, daß man "std::ostream" statt "std::cout" verwendet?

    Fragen über Fragen für die ich bei Dir keine Anworten sehe.

    Grüße



  • ~john schrieb:

    Simon2 schrieb:

    Und die beantwortest Du ganz eindeutig und richtig mit Deinem Wunsch nach "festen Bezügen".

    Es geht hier um Zusicherung von Eigenschaften von Objekten. Wenn ich in einem Programmteil "std::cout" (nur um bei diesem Beispiel zu bleiben) verwende, dann ist nur garantiert, daß der Code damit funktioniert. Wenn ich nun das einfach durch etwas anderes ersetze ist es dann wirklich garantiert, daß das ganze auch noch funktioniert wie vorgesehen? Weshalb wurde dann das Design nicht so gewählt, daß man "std::ostream" statt "std::cout" verwendet?....

    Welche "Eigenschaften" soll denn ein Objekt zusichern und wie soll es das tun ?
    Wohl doch über seine Schnittstelle ... und die wird immer exakt und identisch überprüft - unabhängig davon, ob ich mittels this->/std:: die Suche einschränke oder nicht (bei "std::" vs. "using std::" erhält man sogar dieselbe Suchmenge).

    ~john schrieb:

    ...Wenn ich nun das einfach durch etwas anderes ersetze ist es dann wirklich garantiert, daß das ganze auch noch funktioniert wie vorgesehen? ...

    (Schönes Beispiel, warum Du gegen overloading sein müsstest: Wer garantiert Dir, dass f(int) "dasselbe" macht wie f(char) ?)
    zumm 1000ten Mal: Macht doch nichts, wenn Dir generische Programmierung zu kompliziert erscheint !
    Mir persönlich ist sie das nicht - die Regeln zur Auflösung sind mir eindeutig genug und die entstandene Übersichtlichkeit und Flexibilität wert.

    ~john schrieb:

    Weshalb wurde dann das Design nicht so gewählt, daß man "std::ostream" statt "std::cout" verwendet?....

    Auch das hat überhaupt nichts mit dem Thema zu tun. Ich wollte nie cout durch ostream ersetzen, sondern habe lediglich ein Beispiel gewählt.
    Wenn es Dir so schwer fällt, davon zu abstrahieren, verwende ich zukünftig ein anderes:

    // Version 1
    #include <HerstellerXLib.h>
    #using HerstellerXLib::calculate;
    
    template <typename T>
    T f(T t) {
       T ret = calculate(t);
       t++;
       ret += calculate(t);
       ret.setFlag();
       return ret;
    }
    
    // Version 2
    #include <HerstellerYLib.h>
    #using HerstellerYLib::calculate;
    
    //.... Rest bleibt so wie er war.
    

    ~john schrieb:

    Fragen über Fragen für die ich bei Dir keine Anworten sehe....

    👎 🙄 Vollkommen überflüssige Überdramatisierung, die inhaltlich wohl gar nicht weiterbringt .... 🙄

    Gruß,

    Simon2.


Anmelden zum Antworten