String-Klasse als Vorbild gesucht



  • 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.



  • Keiner hat von try-catch geredet. Ich schreibe bestimmt nicht

    try
    {
        String x = "Hoi!";
        char z = x[-1];
    }
    ...
    

    Ohne try bringt die Exception keinen Overhead, außer sie wird geworfen (und die if-Abfrage natürlich).
    Die if-Abfrage hast du aber auch bei deiner Fluchtwert-Methode.
    Und einen Fluchtwert zurückzuliefern ist ja wohl ne Todsünde. Das Zeichen an Positon 793265 ist nun mal nicht '\0' oder sonst irgendwas. Wenn jemand Mist baut, muss es knallen und zwar sofort.
    Sonst freut sich jemand anders, der dann 3 Stunden lang herausfinden darf, warum der String vorzeitig mit einer '\0' endet.



  • Optimizer schrieb:

    Und einen Fluchtwert zurückzuliefern ist ja wohl ne Todsünde

    Und bei einem ctor auf normalem Wege sowieso nicht machbar.

    Optimizer schrieb:

    Keiner hat von try-catch geredet.

    Imho sorgt catch schon für Overhead. Und wie willst du denn ohne catch Exceptions fangen? Und was passiert, wenn ich kein try verwende? Können dann Exceptions überhaupt gefangen werden?



  • Ja, try-catch sorgt für Overhead. Hat aber keiner gesagt, dass die Exception gefangen werden muss.

    Optimizer schrieb:

    Das Zeichen an Positon 793265 ist nun mal nicht '\0' oder sonst irgendwas. Wenn jemand Mist baut, muss es knallen und zwar sofort.

    Ich will nur darauf hinaus. Ob du das jetzt mit einer ungefangenen Exception oder einem assert machst, ist zumindest mir egal.
    Wichtig ist nur, dass das Programm so schnell wie möglich abkackt und nicht erst über 50 Umwege 2 Jahre später. Dann findet man den Fehler nämlich nicht mehr.



  • Optimizer schrieb:

    Ja, try-catch sorgt für Overhead. Hat aber keiner gesagt, dass die Exception gefangen werden muss.

    Ja dann hat es doch aber auch keinen Sinn, dass ich sie werfe? Was passiert denn, wenn eine Exception geworfen wird und niemand fängt sie?



  • Das Programm kackt ab?



  • Nein. Das Programm wird *korrekt* beendet, mit allen Dtors...das ist IMHO schon ein Unterschied 😉

    Außerdem verwendet man natürlich manchmal try/catch, aber eben nicht oft und gerne ein paar Ebenen höher...



  • operator void schrieb:

    Nein. Das Programm wird *korrekt* beendet, mit allen Dtors...das ist IMHO schon ein Unterschied 😉

    Tatsache. Das sollte man nicht unerwähnt lassen. 🙂



  • operator void schrieb:

    Nein. Das Programm wird *korrekt* beendet, mit allen Dtors...das ist IMHO schon ein Unterschied 😉

    sind wir uns da ganz sicher, daß das programm korrekt beendet wird, wenn keiner die excpetion fängt?



  • Uh-oh, jetzt wo dus sagst...ja, der Standard ist da glaube ich etwas toleranter. Na dann braucht man immerhin ein einziges try/catch im main(), das den Fehler ausgibt (sowieso eine gute Idee). Dass das die Performance runterzieht, bezweifle ich einfach mal.



  • das war doch der sinn der exceptions das man die etliche ebene weiter abfangen kann und eine fehlermedlung oder was auch immer ausgibt. normalerweise steht das dann in der exception klasse welcher und wo der fehler ausgelöst wurde



  • Imho sorgt catch schon für Overhead. Und wie willst du denn ohne catch Exceptions fangen? Und was passiert, wenn ich kein try verwende?

    Ja, try-catch sorgt für Overhead. Hat aber keiner gesagt, dass die Exception gefangen werden muss.

    Ja bei den meisten Compilern produciert try/catch Overheat selbst wenn keine Exception geworfen wird. Wobei ich eigentlich nicht wirklich verstehe wieso man das nicht ohne Overheat hinbekommt.

    Die Dtoren in der Funktion in der throw aufgerufen wird aufzurufen und die catch Blocks zu durchsuchen ist ja wohl kein Problem. Wenn einmal der Stack von localen Daten geputzt ist bleibt immer noch die Returnaddress die man ja nicht nur zum Returnen benutzen kann. Per lookup Table kann man ja herausfinden von wo die Funktion aufgerufte worden ist also welche Dtoren und catch Blöcke gecheckt werden müssen. Wenn das fertig ist hat man wieder eine Return address... Könnte den Code ein wenig aufblähen aber sonst sehe ich eigentlich keine Probleme.

    Nein. Das Programm wird *korrekt* beendet, mit allen Dtors...das ist IMHO schon ein Unterschied

    Die Dtoren globaler Objekte ausgenommen. Wenn eine Exception aus main rausfliegt dann wird der "terminate" Handler aufgerufen. Den kann man mit set_terminate setzen. Bei default ruft der C's abort Funktion auf.

    Also man merke main sollte immer so aussehen:

    int main(){
      try{
        //...
      }catch(exception&except){
        cerr<<"Stopped on exception throw : "<<except.what()<<endl;
        return 1;
      }catch(...){
        cerr<<"Stopped on unknown exception throw!"<<endl;
        return 1;
      }
      return 0;
    }
    

    Dann ist man sicher, dass die Fehlermeldung ausgegeben wird und, dass alle Dtorn aufgerufen werden, da keine Exception aus main rausfliegen kann.



  • Es heißt Overhead. 😉
    Der kommt daher, dass das Programm jedesmal, wenn es in einen try-Block eintritt, das registrieren muss. I.d.R. haben die Programme einen Stack mit try-Eintritten, so dass immer der letzte die Exception fängt, oder eben der eins davor, wenn es kein passendes catch gibt...
    Dazu müssen jedesmal alle lokalen Destruktoren bekannt sein, das gibt es alles nicht umsonst.

    In Java wird das Problem über finally gelöst, bzw. kümmert sich um den Speicher schon mal die Garbage Collection. Ob die VM auch die try-Eintritte einzeln registriert, würd mich auch mal interessieren.



  • Wie ich seh hast du entweder meinen Post nicht gelesen oder nicht verstanden. Ich sehe teschnisch keine Probleme das ohne try Block registrirung zu machen.

    Beispiel:

    void foo(){
      string a;
      throw 4;
    }
    void foo2(){
      try{
        string b;
        foo();
      }catch(int){
      }
    }
    

    In foo selbst kann man den Cleanup von a inlinen. Somit ist keine Registrirung erforderlich. Wenn die Exception aus foo rausfliegt kann sie mit der Returnaddresse feststellen, wohin sie fliegen wird (ohne wirklich dochthin zu fliegen). Da die Returnaddesse ja bereits at Compiletime bekannt ist, kann der Compiler ein Lookup Table anlegen in dem gespeichert ist in welche Funktion die Exception fliegen wird und was da für Dtoren und catchs sind. Somit muss der try Block nicht at runtime registriert werden. Wahrscheinlich kann man damit Exceptions auch schneller fliegen lassen. Der grosse Nachteil ist natürlich, dass jeder Funktionsaufrufen in ein Lookup Table eingetragen werden muss und somit braucht der Code mehr Platz.

    PS: Wer Schreibfehler findet darf die behalten.



  • Das funktioniert nicht.

    void foo()
    {
    try...
    {
    }catch (a){}
    
    try...
    {
    }
    catch(a)
    {}
    catch(b)
    {}
    
    // zur abwechslung mal ein throw ohne umgebenden block in dieser funktion
    throw ThisCodeIsShitException();
    
    try...
    {
    }
    }
    

    Wie ich seh hast du entweder meinen Post nicht gelesen oder nicht verstanden.

    Nö, du glaubst es mir nur nicht, dass man um die Registrierung der einzelnen Eintritte nicht herum kommt.



  • Kannst du mal etwas genauer erklären was da nicht gehen soll? Wer weis von wo nach wo die Exception fliegen soll weis welche catchs und Dtoren auf dem Weg sind.

    Bei deinem foo läst sich das wunderbar machen. Weder catch noch Dtor sind auf der Flugbahn.



  • Es gibt da einige Punkte, an denen ich mich stoße:

    Da die Returnaddesse ja bereits at Compiletime bekannt ist

    Wieso ist sie das? Warum weißt du zur Kompilierzeit, ob foo() jetzt von bar1() oder bar2() aufgerufen wurde? Gehst du jetzt einfach mal von Inlining aus?

    Du gehst anscheindend auch immer davon aus, dass beim Werfen einer immer die gleichen Objekte zerstört werden. Was zerstört wird, kann jedoch sogar noch vom catch-Block abhängen.

    void foo()
    {
        string x;
        try   { throw BlubberException() }  // zerstör nichts
    catch( BlubberException& )
        {
            if( blubb )
                throw;  // zerstör x
            // zerstör nichts
        }
    
        foo2(y);
        if( bla )
            throw BlubberException();  // zerstör x und y
    
        // ...
    }
    

    Wie willst du diese ganzen Fälle unterscheiden? Du kannst nicht einfach sagen, wenn die BlubberException auftritt, musst du das und as zerstören. Du musst genau wissen, in welchem Block du bist und um was für eine Exception es sich handelt.

    Dann noch was:
    Angenommen throwMe() wirft ne Exception. catchMe() würde sie auffangen

    Exception at throwMe()
    at bla()
    at blubb()
    at main()

    Hier wird nichts gefangen. Das musst du wissen. Das weißt du aber nicht. Mit deinem System kennst du nur die Funktion wo du gerade bist und welche Exception jetzt anliegt.

    Exception at throwMe()
    at bla()
    at catchMe()
    at blubb()
    at main()

    Hier wird sie gefangen. Weil blubb() diesmal (d.h. weil rand()%2 diesmal 0 war) nicht direkt throwMe() aufgerufen hat sondern erst catchMe().

    Exception at throwMe()
    at hoi()
    at bla()
    at catchMe()
    at blubb()
    at catchMe()
    at main()

    Jetzt wirds noch schöner. Jetzt musst du wissen welches catchMe das auffängt. Das ist nicht unwesentlich. Was willst du machen?



  • Da die Returnaddesse ja bereits at Compiletime bekannt ist

    Wieso ist sie das? Warum weißt du zur Kompilierzeit, ob foo() jetzt von bar1() oder bar2() aufgerufen wurde? Gehst du jetzt einfach mal von Inlining aus?

    void foo(int a){
      if(a==1)
        throw 4;
    }
    void foo2(){
      foo(1);
    }
    
    foo_ret_table:
      cmp eax foo2.retaddress
      jne .next
        ;foo wurde as foo2 aufgerufen
      .next:
    
    foo:
      cmp [esp+4] 1
      jne .else
        pop eax    ;Return Address
        mov ebx 4  ;die Exception
        add esp 4  ;Mach den Stack sauber von Argumenten (Dtoren)
        jmp foo_ret_table
      .else:  
      ret 4
    
    foo2:
      push 1
      call foo
      .retaddress:
    

    call foo is eigentlich das gleiche wie:

    push .retaddress
    jmp foo
    

    Und das ret 4 in foo tut dies:

    pop eax
    add esp 4
    jmp eax
    

    (ohne eax zu verändern)

    foo und foo2 sind nur labels die an den Stellen wo man sie benutzt durch Numbern ersetzt werden, wie das jetzt genau lauffähig ist weis ich nur es sind immer die gleichen Numberen!

    Du gehst anscheindend auch immer davon aus, dass beim Werfen einer immer die gleichen Objekte zerstört werden. Was zerstört wird, kann jedoch sogar noch vom catch-Block abhängen.

    Ich hab nie gesagt, dass verschiedene throws in der gleichen Funktion nicht verschiedene Dtoren aufrufen können, sondern die die für ein bestimmt throw auf der Flugbahn liegen:

    void foo(){
      string a;
      {
        string b;
        throw 4;
      }
      throw 2;
    }
    
    foo:
      sub esp sizeof(string)   ;string a
      push esp
      call string::string
    
        sub esp sizeof(string) ;string b
        push esp
        call string::string
    
        push esp               ;throw 4
        call string::~string
        add esp sizeof(string) 
        push esp
        call string::~string
        add esp sizeof(string) 
        pop eax
        mov ebx 4
        jmp foo_ret_table
    
        push esp
        call string::~string
        add esp sizeof(string) 
    
      push esp               ;throw 2
      call string::~string
      add esp sizeof(string) 
      pop eax
      mov ebx 2
      jmp foo_ret_table  
    
      push esp
      call string::~string
      add esp sizeof(string) 
    
      ret
    

    Die throws blähen den Code zwar ein wenig auf aber die kann man ja auch noch auslagern wenn man Angst vor langen jmps im normalen Code hat.

    Du musst genau wissen, in welchem Block du bist und um was für eine Exception es sich handelt.

    Ich code jetzt nicht noch mal einen Beispiel zusammen aber im foo_ret_table stehten die Funktionsaufrufe (!=Funktion) und es ist at compile time klar in welchem Block ein Funktionsaufruf steht.

    Das musst du wissen. Das weißt du aber nicht. Mit deinem System kennst du nur die Funktion wo du gerade bist und welche Exception jetzt anliegt.

    Nein du weis auch noch wo du in der Funktion bist. Ich hab jetzt kein Beispiel mit einem catch Block gecodet, aber zwischen den Dtoren muss man nur die typeids vergleichen und man weis ob man weiter fliegen muss oder in den catch Block springen.

    Jetzt wirds noch schöner. Jetzt musst du wissen welches catchMe das auffängt. Das ist nicht unwesentlich. Was willst du machen?

    Das ist ein non Problem. Der einzige Unterschied zwischen 2 zweimal dem gleichen catch Block ist, dass noch ein paar mehr Daten auf dem Stack sind. Wenn man die nicht wegputzt ist alles ok, und ich putz sie ja nicht weg.


Anmelden zum Antworten