Exception



  • CSpille schrieb:

    Marc-O schrieb:

    catch( bad_exception exc ) {  // 1te mal gefangen
      throw;  // Direktes weiterwerfen
    }
    

    Dann fang aber auch Referenzen 🙄
    catch( bad_exception**&** exc )

    catch( bad_exception& exc ) {  // 1te mal gefangen
      throw;  // Direktes weiterwerfen
    }
    

    EDIT:
    Eigentlich sogar const Referenzen
    catch( const bad_exception**&** exc )

    Okay okay 🙂 vergess es jedesmal wieder ;). Werds mir aber merken.

    Mfg marco



  • wie gehts das überhaupt vonstatten.

    Wenn ich mehrere catch Blöcke habe. Liest der Compiler von oben nach unten
    und nimmt den erstbesten der passen könnte ?


  • Mod

    blurry333 schrieb:

    wie gehts das überhaupt vonstatten.

    Wenn ich mehrere catch Blöcke habe. Liest der Compiler von oben nach unten
    und nimmt den erstbesten der passen könnte ?

    Was meinst du? Es gibt nur Verschachtelungen. Und da kommt dann als erstes der in der passenden Schachtelungsebene dran.

    try  // Äußerer Block
    {
    
      // Anweisungen im äußeren Block
    
      try 
       {
          // Innerer Block A
       }
       catch(...)
       {
         // Fehlerbehandlung von Block A
       }
    
      // mehr Anweisungen im äußeren Block
    
      try 
       {
          // Innerer Block B
       }
       catch(...)
       {
         // Fehlerbehandlung von Block B
       }
    
      // noch mehr Anweisungen im äußeren Block
    
    }
    catch (...)
    {
     // Catch vom äußeren Block
    }
    

    Wird bei irgendwelchen Anweisungen im äußeren Block etwas geworfen, wird im äußeren Block gefangen.

    Wird in Block A geworfen, wird in Block A gefangen.

    Wird in Block B geworfen, wird in Block B gefangen.

    Wird in der Fehlerbehandlung von Block A geworfen (z.B. ein rethrow), dann wird vom äußeren Block gefangen.

    Wird in der Fehlerbehandlung von Block B geworfen, dann wird vom äußeren Block gefangen.



  • SeppJ schrieb:

    blurry333 schrieb:

    wie gehts das überhaupt vonstatten.

    Wenn ich mehrere catch Blöcke habe. Liest der Compiler von oben nach unten
    und nimmt den erstbesten der passen könnte ?

    Was meinst du? Es gibt nur Verschachtelungen.

    Nein, es ist möglich, mehrere catch-Blöcke zu schreiben:

    try
    {
        ...
    }
    catch(int i)
    {
        ...
    }
    catch(char* str)
    {
        ...
    }
    catch(const FileNotFoundException& e)
    {
        ...
    }
    catch(const std::exception& e)
    {
        ...
    }
    catch(...)
    {
        ...
    }
    

    Und um die Frage zu beantworten: Ja, der Compiler nimmt den ersten passenden Block. Das heißt, wenn man z.B. den ...-Block als erstes nehmen würde, würde dieser immer genommen werden und die anderen hätten keine Wirkung.



  • also die Ausnahme wird behandelt und dann aufgrund des am Ende stehenden
    throw nochmal geworfen und von einer anderen Klasse behandelt ?



  • Die Exception wird "eine Ebene höher" geschickt. Das wäre vom Resultat wie, als würde der try-catch-Block gar nicht existieren, blos dass eben vorher noch die Anweisungen im catch-Block ausgeführt werden. Das kann nötig sein, wenn man in einer Funktion noch z.B. Resourcen freigeben muss, die eigentliche Exception aber nicht behandeln kann. Beispiel:

    void crazy_func()
    {
        int *needed_array = new int[rand()%1000 + 2000];
        try
        {
            // Tue irgendwas, was eventuell eine Exception werfen könnte
    
            // Aufräumen (an dieser Stelle kommt man nur an, wenn es keine Exception gab)
            delete[] needed_array;
        }
        catch(...)
        {
            // Aufräumen im Falle einer Exception
            delete[] needed_array;
    
            // eigentliche Exception kann in dieser Funktion nicht behandelt werden, also weiter an Aufrufer werfen
            throw;
        }
    }
    

    Mit RAII kann man viele dieser Fälle vermeiden. Das Beispiel könnte auch so aussehen:

    void crazy_func()
    {
        boost::scoped_array<int> needed_array(new int[rand()%1000 + 2000]);
        // Tue irgendwas, was eventuell eine Exception werfen könnte, diesmal ohne try-catch-Block
    }   // das Array wird automatisch freigegeben, egal ob es eine Exception gab oder nicht
    

    Vom Effekt her ist das 2. Beispiel identisch, die Exception wird an den Aufrufer weitergegeben, weil sie innerhalb der Funktion nicht behandelt wird.



  • ipsec schrieb:

    [...]
    Mit RAII kann man viele dieser Fälle vermeiden. Das Beispiel könnte auch so aussehen:

    void crazy_func()
    {
        boost::scoped_array<int> needed_array(new int[rand()%1000 + 2000]);
        // Tue irgendwas, was eventuell eine Exception werfen könnte, diesmal ohne try-catch-Block
    }   // das Array wird automatisch freigegeben, egal ob es eine Exception gab oder nicht
    

    Oder gleich so:

    void crazy_func()
    {
        std::vector<int> needed_array(rand()%1000 + 2000);
        // Tue irgendwas, was eventuell eine Exception werfen könnte, diesmal ohne try-catch-Block
    }   // das Array wird automatisch freigegeben, egal ob es eine Exception gab oder nicht
    

    PS: 🤡



  • Warum fangt ihr const? Wenn ich ne Exception weiterwerfe, will ich ihr meistens weitere Informationen mit auf den Weg geben.



  • Michael E. schrieb:

    Warum fangt ihr const? Wenn ich ne Exception weiterwerfe, will ich ihr meistens weitere Informationen mit auf den Weg geben.

    Klar möchte ich auch weitere Informationen, allerdings (i. d. R.) nur
    Informationen und keine Sachen, die das Objekt verändern.

    Wenn du natürlich in der Exception nen Counter hast, wie oft sie
    schon weitergeworfen wurde und den erhöhst, brauchst du eine non-const Referenz

    Gegenfrage: Wann verwendest du const-Referenzen?



  • CSpille schrieb:

    Michael E. schrieb:

    Warum fangt ihr const? Wenn ich ne Exception weiterwerfe, will ich ihr meistens weitere Informationen mit auf den Weg geben.

    Klar möchte ich auch weitere Informationen, allerdings (i. d. R.) nur
    Informationen und keine Sachen, die das Objekt verändern.

    Versteh ich nicht. Wenn ich dieselbe Exception weiterwerfen will (also ein pures "throw;" schreiben will) un dieser Exception weitere Informationen geben will, muss ich sie auch verändern.

    Gegenfrage: Wann verwendest du const-Referenzen?

    Nie, weil ich keinen Sinn darin sehe. Ich bekomm eh ne Kopie übergeben.



  • Michael E. schrieb:

    CSpille schrieb:

    Michael E. schrieb:

    Warum fangt ihr const? Wenn ich ne Exception weiterwerfe, will ich ihr meistens weitere Informationen mit auf den Weg geben.

    Klar möchte ich auch weitere Informationen, allerdings (i. d. R.) nur
    Informationen und keine Sachen, die das Objekt verändern.

    Versteh ich nicht. Wenn ich dieselbe Exception weiterwerfen will (also ein pures "throw;" schreiben will) un dieser Exception weitere Informationen geben will, muss ich sie auch verändern.

    Korrekt! Aber veränderst du das Objekt in der Regel wirklich?
    Schreibst du weitere Infos in das Objekt oder veränderst vorhandene?
    Du holst dir meistens die Fehlermeldung und einen Error-Code
    oder kuckst dir evtl. noch andere Daten an.
    Ein Weiterwerfen ist wohl keine Veränderung des Objektes.
    und wenn mir das 'const' bei einer Fehleranalyse nur sagt:
    "Don't worry, I won't change your object in this catch-block"
    ist es für mich genug ein 'const' davor zu schreiben

    Michael E. schrieb:

    Gegenfrage: Wann verwendest du const-Referenzen?

    Nie, weil ich keinen Sinn darin sehe. Ich bekomm eh ne Kopie übergeben.

    Über diese Antwort bin ich etwas erstaunt, zumal sie von einem Benutzer mit
    3500 Beiträgen kommt.
    Ich rede von const-Referenzen im Allgemeinen, nicht im Zusammenhang mit Exceptions.
    Ich vermute das führte dich zu deiner Aussage 'Nie', oder?

    Als Beispiel, dass du keine Kopie kriegst:

    #include<iostream>
    
    class MyException{
    public:
            int i;
            MyException(){i=0;}
            MyException(const MyException& e){
                            std::cout << "create copy" << std::endl;
            }
    };
    
    int main() {
            try{
                    try {
    //                      MyException e;
    //                      e.i=0;
    //                      throw e;
                            throw MyException();
                    }
                    catch( MyException& exc ) {
                            std::cout << exc.i << std::endl;
                            exc.i = 1;
                            throw;
                    }
            }catch(MyException& exc){
                    std::cout << exc.i << std::endl;
            }
            return 0;
    }
    
    0
    1
    

    Du kriegst (evtl.) eine Kopie von der Original-Exception,
    aber diese wird gemacht um das Objekt an einen sicheren Ort zu transferieren

    EDIT: Das Objekt wird (bei meinem Compiler) zum Beispiel kopiert, wenn du
    die auskommentierte Version verwendest.
    EDIT2: Ich hab keine Ahnung, ob es beim Weiterschmeißen eine Garantie gibt,
    dass das Objekt nicht kopiert wird, jedoch sollte eine Kopie auch die veränderten
    Daten enthalten.

    Gruß,
    CSpille



  • CSpille schrieb:

    Korrekt! Aber veränderst du das Objekt in der Regel wirklich?

    Manchmal.

    und wenn mir das 'const' bei einer Fehleranalyse nur sagt:
    "Don't worry, I won't change your object in this catch-block"
    ist es für mich genug ein 'const' davor zu schreiben

    Wem genau sagst du, dass sein Objekt nicht verändert wird? Es gibt nur zwei Fälle:

    1. Du wirfst ein Objekt, das schon vorher existiert hat. Dann wird garantiert eine Kopie beim ersten Werfen erstellt, sodass sich das Originalobjekt nicht ändern kann.
    2. Ich erstelle extra ein Objekt, das ich dann durch die Gegend werfe.

    So oder so habe ich also in einem Catch-Block ein Objekt, das nur in Catch-Blöcken leben kann, es hat weder vor dem ersten Wurf gelebt, noch nach dem letzten Fangen. Nun ist es bei Catch-Blöcken so, dass ich nicht weitere Catch-Blöcke aufrufe wie man das bei normalen Funktionen macht, sondern ein "Aufruf" eines anderen Catch-Blocks bedeutet automatisch, dass der aufrufende Catchblock verlassen wird. Also bekommt der Aufrufer das Objekt nie mehr zu sehen, weshalb er sich nicht mehr für Veränderungen am Objekt interessieren kann. Welchen Sinn sollte dann also ein const haben?

    Ich vermute das führte dich zu deiner Aussage 'Nie', oder?

    Ja, ich rede nur vom const beim Fangen von Exceptions.

    Als Beispiel, dass du keine Kopie kriegst:

    Das ist klar.

    EDIT2: Ich hab keine Ahnung, ob es beim Weiterschmeißen eine Garantie gibt,
    dass das Objekt nicht kopiert wird

    Genau dafür wurde "throw;" erfunden. Es wird garantiert dasselbe Objekt weitergeworfen.

    Edit: Zweideutiges "Wem sagst du das denn?" rausgenommen.



  • Michael E. schrieb:

    So oder so habe ich also in einem Catch-Block ein Objekt, das nur in Catch-Blöcken leben kann, es hat weder vor dem ersten Wurf gelebt, noch nach dem letzten Fangen. Nun ist es bei Catch-Blöcken so, dass ich nicht weitere Catch-Blöcke aufrufe wie man das bei normalen Funktionen macht, sondern ein "Aufruf" eines anderen Catch-Blocks bedeutet automatisch, dass der aufrufende Catchblock verlassen wird. Also bekommt der Aufrufer das Objekt nie mehr zu sehen, weshalb er sich nicht mehr für Veränderungen am Objekt interessieren kann. Welchen Sinn sollte dann also ein const haben?

    Kuck nochmal auf mein Beispiel.
    Im Fall dass ich die Exception weiterwerfe, interessiert mich die Änderung
    im nächsten catch-Block...

    Außerdem kannst du ja (theoretisch - praktisch eher selten) einen Verweis
    auf irgendein länger lebendes Heap-Objekt in deiner Exception haben.
    Ohne const, könnstest du auf dieses Objekt ja auch schreibenden Zugriff erlangen.
    Die Änderung dieses Objektes wird dich in deinem späteren Programmverlauf sicher
    interessieren...



  • CSpille schrieb:

    Kuck nochmal auf mein Beispiel.
    Im Fall dass ich die Exception weiterwerfe, interessiert mich die Änderung
    im nächsten catch-Block...

    Ja, aber im vorherigen catch-Block nicht mehr. Wo genau hilft mir jetzt ein const?

    Außerdem kannst du ja (theoretisch - praktisch eher selten) einen Verweis
    auf irgendein länger lebendes Heap-Objekt in deiner Exception haben.
    Ohne const, könnstest du auf dieses Objekt ja auch schreibenden Zugriff erlangen.
    Die Änderung dieses Objektes wird dich in deinem späteren Programmverlauf sicher
    interessieren...

    Nein, das kann man ja gerade nicht, denn beim ersten Werfen wird immer eine Kopie angelegt:

    void foo()
    {
        static int bar = 42;
        throw bar;    // Kopie!
    }
    

    Wenn ich in diesem Fall per Referenz fange, wird einmal kopiert, wenn ich per Wert fange, wird (theoretisch) zweimal gefangen.



  • Michael E. schrieb:

    Nein, das kann man ja gerade nicht, denn beim ersten Werfen wird immer eine Kopie angelegt

    Du hast mich scheinbar nicht richtig verstanden...

    Hier ein Beispiel, was ich meine:

    #include<iostream>
    #include<vector>
    #include<string>
    
    class MyObject{
    public:
            MyObject(const std::string& name) : name(name){
            }
    
            void setName(const std::string& name){
                    this->name = name;
            }
    
            const std::string& getName() const{
                    return this->name;
            }
    
    private:
            std::string name;
    };
    
    class DuplicateNameException{
    public:
            DuplicateNameException(MyObject& object) : object(object){
            }
    
            MyObject& getObject(){
                    return object;
            }
    
            const MyObject& getObject() const{
                    return object;
            }
    
    private:
            MyObject& object;
    };
    
    void createObject(std::vector<MyObject*>& objectPool, const std::string& name){
            for(std::vector<MyObject*>::const_iterator it = objectPool.begin(); it!=objectPool.end(); ++it){
                    if(name == (*it)->getName()){
                            throw DuplicateNameException(**it);
                    }
            }
            objectPool.push_back(new MyObject(name));
    }
    
    int main() {
            std::vector<MyObject*> objectPool;
            try{
                    createObject(objectPool, "test1");
                    createObject(objectPool, "test2");
                    createObject(objectPool, "test3");
                    createObject(objectPool, "test2");
            }
            catch(DuplicateNameException& e){
                    // what shall I do? Probably rename original object?
                    e.getObject().setName("new name");
            }
    //      catch(const DuplicateNameException& e){
    //              // what shall I do? Probably rename original object?
    //              e.getObject().setName("new name"); // Ooh... I can't rename it...
    //      }
            // Let's have a look at the objects (before deleting)
            for(std::vector<MyObject*>::const_iterator it = objectPool.begin(); it!=objectPool.end(); ++it){
                    std::cout << (*it)->getName() << std::endl;
                    delete *it;
            }
            return 0;
    }
    

    Verstehst du jetzt was ich meine?

    Von dem Pointer wird natürlich eine Kopie angelegt, aber das Objekt
    darin hat einen anderen Besitzer, folglich wird es nicht kopiert.

    Ich weiß, die Objektzugehörogkeit ist RAII technisch nicht 100% sauber,
    aber ich hätte das gleiche mit einer Pool-Klasse machen können.
    Das Prinzip bleibt das gleiche, nur halt komplexer...



  • Da hast du recht: Wenn man innerhalb des geworfenen Objekts irgendwelche Zeiger oder Referenzen hat, dann werden natürlich nur die Verweise kopiert. Meine Exception-Klassen waren nie so komplex, dass mir das aufgefallen wäre (und ich hab mir deshalb nicht so tiefgehend Gedanken gemacht 🤡 ). Wieder was gelernt.



  • Also ich fange immer per const&, da ich keinen Sinn darin sehe, eine gefangene Exception zu ändern. Egal ob ich sie danach weiterwerfen möchte oder nicht.

    Wenn es an bestimmten Stellen Sinn machen sollte, dann würde ich genau dort das const weglassen und sonst nirgends.

    Genau so mache ich es auch bei Funktionen: nur dort das const weglassen, wo es auch wirklich weggelassen werden sollte.


Anmelden zum Antworten