String-Klasse als Vorbild gesucht



  • @code-hacker.
    wollte nur mal fragen "wie" du auf die klasse bekommen bist
    also auch nua weil ich mal fast die gleiche machen musste in
    André Willms ->C++ Programmierung -->Kapitel 12
    würd mich halt nua mal interessieren



  • str += 'a'; //zu messende Aktion 1
    estr += 'a'; //zu messende Aktion 2
    

    Anzahl Schleifendurchlaeufe? 10000
    ...
    Durchschnitts-Zeit bei Aktion 1: 0.00206667 sec

    Zeit bei Aktion 2: 0.2 sec
    Zeit bei Aktion 2: 0.671 sec
    Zeit bei Aktion 2: 1.662 sec
    Zeit bei Aktion 2: 3.095 sec
    Zeit bei Aktion 2: 4.416 sec
    Zeit bei Aktion 2: 5.388 sec
    Zeit bei Aktion 2: 6.419 sec
    Zeit bei Aktion 2: 7.521 sec
    Zeit bei Aktion 2: 9.393 sec
    Zeit bei Aktion 2: 9.694 sec
    Zeit bei Aktion 2: 11.176 sec
    Zeit bei Aktion 2: 12.739 sec
    Zeit bei Aktion 2: 13.799 sec
    Zeit bei Aktion 2: 14.932 sec
    Zeit bei Aktion 2: 16.113 sec
    Durchschnitts-Zeit bei Aktion 2: 7.81453 sec



  • Hi!

    @truebool:
    Das ist die Klasse aus "GoTo C++ Programmierung" Kapitel 23.

    @Erhard Henkes:
    std::string ist ja noch hier und da optimiert und kann auch je nach Compiler anders implementiert sein und optimiert. Und ich glaube die Klasse EString ist sehr sicher, als das man da sicher einiges weglassen könnte um performance zu gewinnen.

    Code-Hacker



  • Selbst bei den einfachen vergleichsoperatoren ist std:string 10 mal schneller:

    string   str ="Hallo";
      EString estr ="Hallo";
      bool    retVal;
    
      retVal = (str > str);   //zu messende Aktion 1
      retVal = (estr > estr); //zu messende Aktion 2
    

    Anzahl Schleifendurchlaeufe? 10000000
    Durchschnitts-Zeit bei Aktion 1: 0.0220667 sec
    Durchschnitts-Zeit bei Aktion 2: 0.194267 sec

    die Klasse EString ist sehr sicher

    🙄 😕

    Übrigens ist ein Fehler in der Klasse EString:

    EString estr;
    retVal = (estr > estr); // Fehlermeldung bei allen Vergleichsoperatoren!
    

    z.B.:

    int main()
    {
      EString estr;
      cout << (estr <= estr);
      getch();   
    }
    

    das liegt an dem Konstruktor ohne Argument, der string = 0 setzt, siehe:

    #include <iostream>
    #include <conio.h>
    #include <cstring>
    using namespace std;
    
    int main()
    {
        cout << strcmp(0,0); // Fehlermeldung! 
        getch();
    }
    

    Konstruktor ohne Argument:

    EString::EString()
    {
    	len=0;
    	bufsize=0;
    	string=0; 
    }
    
    /*
    ** Vergleichsoperatoren
    */
    
    bool EString::operator<(const EString &s) const
    {return(strcmp(string,s.string)<0);}
    
    bool EString::operator<=(const EString &s) const
    {return(strcmp(string,s.string)<=0);}
    
    bool EString::operator==(const EString &s) const
    {return(strcmp(string,s.string)==0);}
    
    bool EString::operator!=(const EString &s) const
    {return(strcmp(string,s.string)!=0);}
    
    bool EString::operator>=(const EString &s) const
    {return(strcmp(string,s.string)>=0);}
    
    bool EString::operator>(const EString &s) const
    {return(strcmp(string,s.string)>0);}
    

    Entweder muss man den Konstruktor oder die Vergleichsoperatoren ändern. Man müsste string==0 || s.string==0 abfangen, da strcmp offensichtlich keine Nullzeiger akzeptiert. Leicht zu erreichen.

    Wie man sieht, ist die Klasse EString alles andere als performant oder ausgereift! ⚠



  • Hi!

    @Erhard Henkes:
    Mit "die Klasse ist sehr Sicher" meine ich das überall eine Sicherheitsabfrage drin ist bevor irgendetwas gemacht wird. Z.B. beim Indexoperator, dort wird vorher mit assert geprüft ob der Index gültig ist, eine Bereichsprüfung macht auch nur std::string::at.

    Naja, das die Klasse nicht an std::string rankommt war eigentlich klar, was solls.

    Die Vergleichoperatoren greifen auf strcmp zurück. Würde mich mal interessieren was die std::string-Klasse wesentlich anders macht.

    Code-Hacker



  • Du brauchst doch nur nachsehen, was die anders machen.



  • Also mir gefällt die String Klasse überhaupt nicht, und die Geschwindigkeitsprobleme ergeben sich aus einigen grundsätzlichen Designfehlern. Nur mal ein paar kleine Verbesserungsvorschläge:
    1. Schmeiss die Struktur der nullterminierten C-Strings weg und implementier die Daten der Zeichenkette als ganz normales Array. Die 3 benötigten Member dafür hast du ja. Dann brauchst du auch keine str... Funktionen mehr zu verwenden, die ein Bottleneck der Klasse darstellen. Dafür nimmst du dann die mem... Funktionen, die wesentlich schneller sind. Später lässt sich dann dadurch sogar kinderleicht eine Template basierte Stringklasse entwickeln, mit der man zB auch wchar_t als Zeichentyp nutzen kann. Die einzige str... Funktion die du brauchst, ist strlen (bzw. wcslen oder was auch immer), da es ja auch möglich sein soll ein String Literal bzw C-String zu übergeben. Solltest du deinen String wiederum als C-String brauchen (zB für eine WinAPI Funktion), dann mach es wie std::string und biete dafür eine entsprechende Funktion an (c_str()).
    2. Implementier ínsert und add (+=) separat, da das Hinzufügen somit deutlich performanter ist.
    3. Verwende für Längenangaben bzgl. eines Speicherbereiches std::size_t.
    4.

    EString(const char);
    

    const char zu übergeben macht keinen Sinn (bei einigen anderen Funktionen genauso), da der Parameter sowieso als Kopie vorliegt und der ursprüngliche Wert eh nicht verändert werden kann. Was willst du damit erreichen? Das man den Wert auf dem Stack nicht verändert? Wozu?
    5. Stell einen ctor mit std::size_t Parameter zur Verfügung um einen entsprechend gross vorreservierten String zu erzeugen, dh

    EString s(10);
    

    erzeugt einen String der Länge 0 mit 10 reservierten Zeichen. Das ist praktisch, wenn du genau weisst wie gross dein String maximal wird und somit nur ein mal Speicher reservieren brauchst.
    6. Zwinge deiner Klasse im Header nicht den std Namensraum auf und verwende std:: stattdessen.
    7. Durch besseres Speicher-Handling und damit einer überarbeiteten insert Funktion lässt sich sicherlich auch noch mal einiges rausholen.

    Und um dich jetzt noch ein bisschen zu motivieren 🙂 , ich hab vor einiger Zeit selbst eine String Klasse für Lernzwecke implementiert (ohne expliziten Prozessor Schnickschnack wie MMX, SSE, etc.), die teilweise schneller ist als die std::string Implementation von MS.



  • Der Konstruktor ohne Argument ist nicht richtig implementiert. Auch beim Copy-Konstruktor gibt es damit Probleme.

    EString s; sollte eher analog erstellt werden wie EString s("");

    Man kann die Klasse deutlich(!) beschleunigen, wenn man die Konstante FWDBUFFER erhöht, z.B. auf 100 (oder höher). Dieser Mechanismus kommt mir aber noch irgendwie suspekt vor.

    @ groovemaster2002: poste deine Klasse doch mal hier, damit wir sie so richtig zerlegen können. 😉



  • Erhard Henkes schrieb:

    @ groovemaster2002: poste deine Klasse doch mal hier, damit wir sie so richtig zerlegen können

    War das ironisch oder ernst gemeint? 🙂



  • groovemaster2002 schrieb:

    Erhard Henkes schrieb:

    @ groovemaster2002: poste deine Klasse doch mal hier, damit wir sie so richtig zerlegen können

    War das ironisch oder ernst gemeint? 🙂

    Eher sarkastisch 🙄



  • groovemaster2002 schrieb:

    die teilweise schneller ist als die std::string Implementation von MS.

    wurde die STL von MS implementiert ?



  • DEvent schrieb:

    groovemaster2002 schrieb:

    die teilweise schneller ist als die std::string Implementation von MS.

    wurde die STL von MS implementiert ?

    Es gibt verschiedene Implementierungen, von Microsoft gibt es soweit ich weiß keine. Die Standardbibliothek die mit VC mitgeliefert wird, stamm von Dinkumware.



  • Hi!

    goovemaster2002 schrieb:

    6. Zwinge deiner Klasse im Header nicht den std Namensraum auf und verwende std:: stattdessen.

    Das hat Erhard Henkes so gemacht. Eigentlich ist der namensraum std nur in der CPP-Datei global bekannt gemacht.

    Danke für deine Vorschläge, da ich jetzt etwas ferien habe werde ich die Stringklasse mal erneuern. Habe mal die im Stroustrup kurz überflogen, die sah ganz interessant aus. Dort wird der String über den Konstruktor ohne Argument auch mit einem Leerstring initialisiert.

    Code-Hacker



  • Die Vergleichoperatoren greifen auf strcmp zurück. Würde mich mal interessieren was die std::string-Klasse wesentlich anders macht.

    Naja, normalerweise würde ich erstmal die länge der Strings vergleichen (die ja extra gespeichert ist). Wenn die Länge zweier strings verschieden ist, kann der inhalt nicht gleich sein.



  • Hi!

    Stimmt.
    Außerdem prüft strcmp alle Fälle und nicht nur Gleichheit. Deswegen kann man dort wieder jedes Zeichen nur mit == vergleichen und bei ungleichheit einfach false zurückgeben, ansonsten true.

    Code-Hacker



  • if(string) delete[](string);
    len=v.size();
    bufsize=FWDBUFFER+len+1;
    string=new(char[bufsize]);
    

    Nun stell dir mal vor, dass new eine bad_alloc exception werfen würde. Dann wäre die Stringklasse irreparabel futsch. Ausserdem wäre es besser auch bei einem leeren String Speicher anzulegen, damit fällt der ständige Test ob string non NULL ist weg.

    Erhard Henkes schrieb:

    // []
    char MyString::operator[]( int i ) const
    {
      if( i >= 0 && i < getLaenge() )
      {
        return pData_[i];
      }
      else
      {
        return '\0';
      }
    }
    

    Meinst du dies hier? Eine Referenz auf einen char? hmmm.

    Ich denke, nein.

    Ich würde bei einer Bereichsüberschreitung ne Exception werfen, aber wenn man das nicht will gibt es eine eher gute Alternative. Die Stringklasse kriegt einen statischen char Member und wenn der Bereich überschritten ist dann setzt du ihn auf 0 und gibst ihn als Referenz zurück. Wenn der Benutzer ihn nun auf non 0 setzt ist nicht schlim wenn du ihn wieder verwendest setzt du ihn ja wieder darauf.

    EString &EString::operator=(const char *s)
    {
        replace(s);
        return(*this);
    }
    

    Hier würd ich die replace Funktion direkt in den operator=(const char*) packen (also nicht ihn eine eigene Funktion auslagern). Das ist ja alles was der operator=(const char*) tut, und du kannst ihn ja auch aus anderen Methoden aus aufrufen.

    Also ich bevorzuge std::string (auch vom Design her).



  • Ich würde bei einer Bereichsüberschreitung ne Exception werfen, aber wenn man das nicht will gibt es eine eher gute Alternative. Die Stringklasse kriegt einen statischen char Member und wenn der Bereich überschritten ist dann setzt du ihn auf 0 und gibst ihn als Referenz zurück. Wenn der Benutzer ihn nun auf non 0 setzt ist nicht schlim wenn du ihn wieder verwendest setzt du ihn ja wieder darauf.

    Von so viel Toleranz halte ich nichts. Da muss eiskalt eine Exception fliegen, oder (wenn die Performance mal wieder soooo wichtig ist) ein assert mit dem Risiko, im Release-Build UB zu haben.
    Fluchtwerte halte ich hier grundsätzlich für falsch. Das ist nichts, was mal passieren kann (die Rede ist von einem ungültigen Index), sondern etwas, was absolut nicht passieren darf.



  • Irgendwer schrieb:

    Ich würde bei einer Bereichsüberschreitung ne Exception werfen [...]

    Optimizer schrieb:

    Von so viel Toleranz halte ich nichts. Da muss eiskalt eine Exception fliegen [...]

    Ist doch schön, wenn man sich so einig ist. :p
    Aber mal Spass beiseite. Ich hab in meiner String Klasse selbst so einen static Dummy. Auch wenn eine Exception sicherlich nicht falsch ist, so halte ich das bei Elementzugriffen für ziemlich harten Tobak (zumindest im Release Build). Da geht einem der Spass an C++ gegenüber Sprachen wie VB doch glatt verloren. 😃 Vielleicht liegt es aber auch nur daran, dass ich Exceptions normalerweise nur in Initialisierungs/Deinitialisierungsphasen (wie ctor / dtor) verwende, wo Performance nur eine untergeordnete Rolle spielt.

    Shlo schrieb:

    groovemaster2002 schrieb:

    Erhard Henkes schrieb:

    @ groovemaster2002: poste deine Klasse doch mal hier, damit wir sie so richtig zerlegen können

    War das ironisch oder ernst gemeint? 🙂

    Eher sarkastisch 🙄

    Für Sarkasmus wars mir noch zu harmlos. :p

    Code-Hacker schrieb:

    Habe mal die im Stroustrup kurz überflogen, die sah ganz interessant aus. Dort wird der String über den Konstruktor ohne Argument auch mit einem Leerstring initialisiert.

    Imo ist es eine gute Idee, grundsätzlich Speicher für die String Daten im ctor zu reservieren. Wie Irgendwer schon sagte, kannst du dir dann den ganzen if (!string) Teil sparen und erweiterst (oder verkürzt) lediglich den Speicherbereich bei Bedarf.



  • Vielleicht liegt es aber auch nur daran, dass ich Exceptions normalerweise nur in Initialisierungs/Deinitialisierungsphasen (wie ctor / dtor) verwende, wo Performance nur eine untergeordnete Rolle spielt.

    Auu, nicht so hart bitte. Ein Dtor sollte keine Exceptions werfen! Und das war ja wohl nicht dein Ernst, dass aus Performancegründen keine Exceptions verwendest! Wenn ein Fehler auftritt führt das auf lange Sicht immer zu einem Abbruch der Prozedur in der er aufgetreten ist und dann ist die Performance eh kein Thema mehr.



  • Irgendwer schrieb:

    Auu, nicht so hart bitte. Ein Dtor sollte keine Exceptions werfen!

    Jau, hast natürlich Recht mit dem dtor. Keine Ahnung was mich da geritten hat, meinte auch eher nur ctor. 😃

    Irgendwer schrieb:

    Und das war ja wohl nicht dein Ernst, dass aus Performancegründen keine Exceptions verwendest!

    Doch!

    Irgendwer schrieb:

    Wenn ein Fehler auftritt führt das auf lange Sicht immer zu einem Abbruch der Prozedur in der er aufgetreten ist und dann ist die Performance eh kein Thema mehr.

    Das ist schon richtig, nur hab ich das Gefühl, dass zu viele Leute Exceptions missbrauchen und dort verwenden, wo sie gar nicht notwendig sind. Exception bedeutet Ausnahme, und nicht jeder Fehler führt zu einem nicht wiederherstellbaren Ausnahmezustand eines Objektes. Ausserdem bringen Exceptions generell einen Laufzeitoverhead mit, egal ob sie geworfen werden oder nicht. Dieser hängt natürlich von der Code Struktur und der Implementation ab. Vor einiger Zeit hatte ich mal einen relativ einfachen Benchmark gemacht in einem try / catch Block. Nachdem ich diesen weggelassen hatte, lief das ganze ca. 3 mal zu schnell. Mag jetzt ein Extrembeispiel sein, zeigt aber was generell möglich ist.


Anmelden zum Antworten