Tips für weitere Funktionen in eigener String-Klasse



  • uhsuhz schrieb:

    , mit dem copy-konstruktor, da kann ich ja eigentlich die operator= funktion aufrufen, oder? die soll ja auch eine neue Stringklasse machen und dann alles rüberkopieren und dann zurückgeben, oder?

    Nein, die Aufgabe des Zuweisungsoperators ist etwas weiter gefasst. Dieser Operator ist insbesondere auch dafür zuständig, den existierenden "alten" Inhalt des Objekts ordentlich freizugeben. Das ist etwas, das der Copy-CTor niemals tun muss (oder darf).

    Es gibt den den Ansatz, den Zuweisungsoperator über den Copy-CTor als "Copy & Swap" zu implementieren, d.h. so:

    Stringclass& Stringclass::operator=( const Stringclass& rhs )
    {
        Stringclass temp( ths );
        /*this->*/Swap( temp );
        return *this;
    }
    

    Dazu brauchst du die bereits angesprochene effiziente Swap-Methode. Dieser Ansatz hat den Vorteil, dass du nur den Copy-CTor ausprogrammieren musst, und dass der Zuweisungsoperator dadurch auf die Prüfung auf Selbstzuweisung verzichten kann und exceptionsicher wird.



  • Stringclass& Stringclass::operator=( const Stringclass& rhs )
    {
        Stringclass temp( ths );
        /*this->*/Swap( temp );
        return *this;
    }
    

    Oh, das ist clever! Wieder etwas gelernt. 😋



  • uhsuhz schrieb:

    oje... hätt ich doch bloß nichts gepostet^^ bin jetzt total überfordert...

    ne ne, du musst es anders sehen:
    viele möglichkeiten etwas zu tun, genug ideen damit du lange beschäftigt bist

    was ist denn an den operator+ funktionen falsch?

    der operator+ erstellt ein neues objekt:
    a+b liefert c und ändert a und b nicht.
    du lieferst aber nur eine referenz und kein neues objekt

    ach ja und was ist falsch mit den const-anweisungen?

    einige funktionen wie zB length() verändern das objekt nicht und sollten daher als const markiert sein.

    damit ich

    Stringclass const s("hallo");
    return s.length();
    

    machen kann



  • ach so^^ habs jetzt verstanden, danke für die Hilfe.

    Das mit dem operator+ wird mir jetzt auch klar, hab da nicht nachgedacht.

    Stringclass& Stringclass::operator=( const Stringclass& rhs )
    {
        Stringclass temp( ths ); //<- müsste da nicht rhs stehen, steht jedenfalls oben in der deklaration
        /*this->*/Swap( temp );
        return *this;
    }
    

    Sind die anderen const anweisungen denn korrekt?

    Danke uhsuhz



  • Der string sollte nicht auf ein char == ein Zeichen begrenzt sein, sondern seine Zeichengröße auf den Verwendeten Zeichensatz anpassen.



  • unicoder schrieb:

    Der string sollte nicht auf ein char == ein Zeichen begrenzt sein, sondern seine Zeichengröße auf den Verwendeten Zeichensatz anpassen.

    hää versteh ich jetzt nicht ganz^^ vielleicht könntest du das genauer erklären



  • uhsuhz schrieb:

    hää versteh ich jetzt nicht ganz^^ vielleicht könntest du das genauer erklären

    char kann maximal 255 verschiedene zeichen darstellen. Der Unicodezeichensatz beispielsweise hat aber 100000 Zeichen. du verstehst das Problem? 🙂



  • ach so... hmm das wird mir jetzt aber glaub ich schon ein wenig zu weit^^, ich glaube ich beschränke mich mal auf char, immerhin habe ich noch nicht so viel Erfahrung.Vielleicht kann ich später die klasse um schreiben

    Also habe jetzt mal angefangen die klasse zu schreiben, hab jetzt mal die konstruktore und den destruktor geschrieben, würde gerne wissen ob die schon mal ok sind, also vom aufbau her:

    #ifndef STRINGCLASS_H
    #define STRINGCLASS_H
    
    typedef unsigned int UINT;
    
    class Stringclass
    {
    private:
        char* data;
        UINT size;
    public:
        Stringclass();
        ~Stringclass();
        Stringclass(const Stringclass&);
    
    };
    
    #endif
    
    #include "Stringclass.h"
    
    Stringclass::Stringclass()
    {
        size = 0;
        data = new char[size];
    }
    
    Stringclass::~Stringclass()
    {
        delete[] data;
    }
    
    Stringclass::Stringclass(const Stringclass& sc)
    {
        size = sc.size;
        data = new char[size];
        for(UINT i=0;i<size;i++)
            data[i] = sc.data[i];
    }
    

    Die funktionen funktionnieren zwar alle, aber vielleicht könnte mal es ja anders schreiben oder es könnten fehler später auftreten.



  • 😉 Initialisierungsliste benutzen

    😉

    for(UINT i=0;i<size;i++)
            data[i] = sc.data[i];
    

    Sowas wirst du in der klasse oefters brauchen. du solltest das also in eine funktion auslagern. idealerweise gibt es dafuer zB std::memcpy oder std::copy. Du kannst es natuerlich auch selber schreiben der Uebung wegen, aber eine eigene funktion sollte es schon sein (denn doppelten code will man nie haben. immer wenn du code mehr als einmal brauchst, schreit dass nach abstraktion)

    PS:
    ignoriere das Unicode Problem (also dass char nur 256 Zeichen beinhalten kann) erstmal. das thema ist zu komplex und laengt nur vom eigentlichen thema ab.



  • Shade Of Mine schrieb:

    PS:
    ignoriere das Unicode Problem (also dass char nur 256 Zeichen beinhalten kann) erstmal. das thema ist zu komplex und laengt nur vom eigentlichen thema ab.

    richtig, ich glaub allerdings auch nicht, dass unicoder den Vorschlag ernst meinte :). Andererseits ist es interessant(zumindest wars das für mich), sich mit unicode zu beschäftigen, und einen wtf moment nach dem anderen beim durchlesen der featureliste zu haben 😃



  • danke für die hilfe werde das mal einbauen^^



  • So ich habe jetzt mal bisschen weiter programmiert, und weitere fragen sind aufgetaucht. Ich dokumentiere mal den code.

    void mycopy(const char* from, char* to, UINT size, UINT to_begin = 0)
    {
        for(UINT i=0; i<size;i++)
            to[i+to_begin] = from[i];
    }
    
    Stringclass::Stringclass()
    :size(0)
    {
        data = new char[size]; 
    }
    
    Stringclass::Stringclass(const Stringclass& sc)
    :size(sc.size)
    {
        data = new char[size];
        mycopy(sc.data,data,size);
    }
    
    Stringclass::Stringclass(UINT scsize, const char* scdata)
    :size(scsize)
    {
        data = new char[size];
        mycopy(scdata,data,size);
    }
    
    Stringclass::~Stringclass()
    {
        delete[] data;
    }
    
    Stringclass& Stringclass::operator=(const Stringclass& sc)
    {
        Stringclass temp(sc);
        this->myswap(temp);
        return *this;
    }
    
    char& Stringclass::operator[](UINT index) const
    {
        return this->data[index];
    }
    
    UINT Stringclass::get_size() const
    {
        cout << "Funktion: get_size" << endl;
        return size;
    }
    

    Also zu dem Code besteht jetzt nur eine frage, kann man "data = new char[size]" in die Initialisierungsliste des konstruktors packen?

    So jetzt kommen die Fragen^^

    Also hab mal zuerst ne swap methode geschrieben.Funktionnieren tut es, aber schön sieht das nicht aus, meiner meinung nach, da ich den standart copykonstruktor selber programmieren musste, damit temp auf sc verweist.So brauche ich dann nicht den inhalt, sondern nur den zeiger auf data zu kopieren, ich muss nur den zeiger von temp auf 0 zum Schluss setzen, damit ich den speicher von this nicht lösche.Hab das mal aufgemalt:
    http://img179.imageshack.us/img179/9462/swapda4.jpg

    void Stringclass::myswap(Stringclass& sc)
    {
        Stringclass temp;
        temp.size = sc.size;
        temp.data = sc.data;
        sc.size = this->size;
        sc.data = this->data;
        this->size = temp.size;
        this->data = temp.data;
        temp.data = 0;
    }
    

    und dann noch den sc-realloc:

    void Stringclass::sc_realloc(UINT newsize)
    {
        Stringclass temp(newsize,this->data);
        myswap(temp);
    }
    


  • Naja was du alledings besser machen könntest wäre ne Format funktion, welche mir definitv in der STL string fehlt;) also ne C++ sprintf variante, so wie sie in MFC CString Format realisiert ist...



  • EDIT: Damit sowas geht:

    //format via Konstruktor
    double dDat=12.455;
    Stringclass strdouble("%f",dDat);
    
    //Format via Funktion
    
    Stringclass strdouble;
    
    strdouble.Format("f",dDat);
    


  • CFreak schrieb:

    Naja was du alledings besser machen könntest wäre ne Format funktion, welche mir definitv in der STL string fehlt;) also ne C++ sprintf variante, so wie sie in MFC CString Format realisiert ist...

    Aber bitte nicht wieder mit Ellipsen und Formatkennzeichnern, die den Datentyp beinhalten müssen. Da sollten wir in C++ drüber weg sein.



  • uhsuhz schrieb:

    So ich habe jetzt mal bisschen weiter programmiert, und weitere fragen sind aufgetaucht. Ich dokumentiere mal den code.

    Ganz ordentlich. Statt UINT solltest du "unsigned" nehmen, das ist gängiger. aber im prinzip ist der code ok.

    Also zu dem Code besteht jetzt nur eine frage, kann man "data = new char[size]" in die Initialisierungsliste des konstruktors packen?

    probier es doch aus 🙂
    und ja, es geht.

    aber pass auf, data und size werden unabhängig von ihrer position in der initialisierungsliste sonder nur abhängig von der reihenfolge der deklaration in der klasse.

    Also hab mal zuerst ne swap methode geschrieben.Funktionnieren tut es, aber schön sieht das nicht aus, meiner meinung nach, da ich den stan**** copykonstruktor selber programmieren musste, damit temp auf sc verweist.So brauche ich dann nicht den inhalt, sondern nur den zeiger auf data zu kopieren, ich muss nur den zeiger von temp auf 0 zum Schluss setzen, damit ich den speicher von this nicht lösche.Hab das mal aufgemalt:
    http://img179.imageshack.us/img179/9462/swapda4.jpg

    Den CopyCtor musst du so oder so selber programmieren.

    void Stringclass::myswap(Stringclass& sc)
    {
        Stringclass temp;
        temp.size = sc.size;
        temp.data = sc.data;
        sc.size = this->size;
        sc.data = this->data;
        this->size = temp.size;
        this->data = temp.data;
        temp.data = 0;
    }
    

    std::swap hilft dir hier:

    void Stringclass::myswap(Stringclass& sc)
    {
      std::swap(size, sc.size);
      std::swap(data, sc.data);
    }
    

    du brauchst kein temporaeres Stringclass objekt dafür.

    und dann noch den sc-realloc:

    void Stringclass::sc_realloc(UINT newsize)
    {
        Stringclass temp(newsize,this->data);
        myswap(temp);
    }
    

    Das ist ok so. den vorteil siehst du hoffentlich:
    es ist viel einfach ein realloc so zu machen als händisch alles rumzukopieren. 🙂



  • Erstmal danke an Shade Of Mine, deine Ratschläge haben mir sehr weiter geholfen, hab sehr viel in Bezug zu den klassen gelernt 🙂

    Shade Of Mine schrieb:

    Ganz ordentlich. Statt UINT solltest du "unsigned" nehmen, das ist gängiger. aber im prinzip ist der code ok.

    hmm ich hatte UINT in der header mit typedef deklariert, darum müsste es aber klar sein, oder?

    Shade of Mine schrieb:

    std::swap hilft dir hier:

    void Stringclass::myswap(Stringclass& sc)
    {
      std::swap(size, sc.size);
      std::swap(data, sc.data);
    }
    

    du brauchst kein temporaeres Stringclass objekt dafür.

    Ich würde aber lieber alle Funktionen selber programmieren zur Übung, darum habe ich jetzt mal die Funktion neu geschrieben, sodass kein Temporäres object mehr gebildet wird, sondern nur 2 temporäre Variablen.

    void Stringclass::myswap(Stringclass& sc)
    {
        UINT i_temp = sc.size; //Temporäre UINT-Variable
        sc.size = this->size;
        this->size = i_temp;
    
        char* c_temp = sc.data; //Temporärer Char-Zeiger
        sc.data = this->data;
        this->data = c_temp;
    }
    

    Ich würde mal sagen, dass dies schneller ist, als ein Stringclass-Object zu stellen oder?

    und dann noch den sc-realloc:

    void Stringclass::sc_realloc(UINT newsize)
    {
        Stringclass temp(newsize,this->data);
        myswap(temp);
    }
    

    Das ist ok so. den vorteil siehst du hoffentlich:
    es ist viel einfach ein realloc so zu machen als händisch alles rumzukopieren. 🙂

    Jop natürlich sehe ich dass es einfacher ist, ich hab den code ja auch so selber erdacht und geschrieben und nicht nur abkopiert von einem andern 😃 . Ich habe diesen Konstruktor ja auch eigentlich nur wegen dieser Funktion hinzugefügt am anfang, aber jetzt sehe ich dass er auch sehr gut geeignet ist um Const-Stringclassen mit Werten zu füllen.



  • Mich wundert, dass noch niemand diesen schönen Artikel von Herb Sutter über das Design der std::string-Klasse verlinkt hat:
    Monoliths "Unstrung"



  • ich hab noch ne frage, bei der überladung des >>operators:

    std::istream& operator>>(std::istream& is, Stringclass& sc)
    {
        char x[100];
        std::cin.getline(x,100);
        Stringclass temp(c_len(x),x);
        sc.myswap(temp);
        return is;
    }
    

    kann ich da nicht irgentwie x, als undefiniert groß nehmen, also dass man auch 1000 zeichen tippen kann oder muss man eine bestimmte größe nehmen, das heißt in diesem Fall dass nur 100 Zeichen genommen werden.



  • Shade Of Mine schrieb:

    😉 protected ist wie public: nur verwenden wenn es wirklich sein muss. strings sind aber value typen. und von value typen erbt man nicht. ergo nix protected machen.

    Nicht dass ich deinem Rat widersprechen wollte, aber "strings sind aber value typen" ist nicht so zwangsläufig wie du das dahinstellen willst.

    Shade Of Mine schrieb:

    😉 dir fehlt ein copyctor, beachte the law of the big three

    👍

    Shade Of Mine schrieb:

    😉 deine operator+ funktionen sind falsch

    👍

    Shade Of Mine schrieb:

    😉 generell sollten alle funktionen die nicht zwingend member sein muessen, ausserhalb der klasse definiert werden. darunter zB die vergleichsoperatoren, find, compareto,...

    👍

    Shade Of Mine schrieb:

    😉 du solltest eine schnittstelle anbieten damit man die funktionalitaet der klasse erweitern kann: iteratoren und operator[]

    👍

    Shade Of Mine schrieb:

    😉 du koenntest die performance verbessern indem du mehr speicher allokierst als du jetzt brauchst um so schneller anhaengen zu koennen. oder aber du spendierst hier eine 2. klasse, einen stringbuilder der schnelles anhaengen ermoeglicht und nachher einen string liefert (wie eben zB Java StringBuilder)

    👍

    Shade Of Mine schrieb:

    😉 du solltest auf constant correctness achten

    👍

    Shade Of Mine schrieb:

    😉 eine implementierung fuer swap fehlt, um 2 string objekte effizient miteinander austauschen zu koennen ohne alles kopieren zu muessen

    👍

    Shade Of Mine schrieb:

    😉 index, length, etc sollten unsigned int sein, damit verhinderst du automatisch dass jemand negative werte uebergibt

    Ja, ich plädiere ebenfalls für std::size_t oder sowas 👍


Anmelden zum Antworten