Exception (Klasse) wird nicht ausgelöst



  • Hallo liebe Programmierer,

    Es geht um eine selbst erstellte Array-Klasse, die genau wie bei der Vektor-Klasse, eine at() Funktion beinhaltet, die den Wert des übergebenen Indizes zurückgibt. Für den Fall, daß ein Wert größer oder gleich des Arrays übergeben wird, soll eine Ausnahmefall ausgelöst werden, der durch eine Exception-Klasse abgefangen und mit einer ensprechenden Ausgabe an den Anwender gemeldet wird. Also eigentlich etwas ganz einfaches.

    Ausser einigen Kleinigkeiten programmiere ich ausschliesslich in C. Ein Freund bat mich vor einigen Tagen um Hilfe bei seiner Hausarbeit, die er in C++ umsetzen sollte. Mit Unterstützung von einigen Büchern habe ich das Programm auch gut hinbekommen. Bloß die Exception-Klasse treibt mich in den Wahnsinn. Ich mache es anscheinend genauso wie in den Lehrbüchern. Aber es funktioniert leider nicht.

    Die Auszüge aus dem Quellcode
    -------------------------------

    Die Exception Klasse:

    class OutOfRange {
       private:
          int index;
       public:
          OutOfRange(int n)     { set_index(n); }
          ~OutOfRange()         {               }
          void set_index(int n) { index= n;     }
          int get_index()       { return index; }
          void info()           { cout << endl << "Out of index!" << endl << "   Pos: " << get_index() << endl; }
    };
    

    Die at-Funktion

    int Array::at(int index) {
       int n= size();
       if (index < 0 || index >= n) {
          throw OutOfRange(index);
       }
       return carray[index];
    }
    

    Auszug aus der Main-Funktion

    int main() {
    
        .
        .
        .
    
        try
        {
           cout << a.at(9) << endl;
        }
        catch (OutOfRange& e)
        {
           e.info();
        }
        .
        .
        .
        return 0;
    }
    

    Wenn ihr mir mich auf meinen Fehler aufmerksam machen oder eine bessere Lösung bieten könntet würde ich mich sehr freuen.

    Grüße



  • was sagt der debugger. Compiliert der code überhaupt? Was ist die Ausgabe?

    versuch mal ein

    throw new OutOfRange(index);

    und

    catch(OutOfRange* e)

    dürfte aber auch nichts bringen



  • Maxi schrieb:

    was sagt der debugger. Compiliert der code überhaupt? Was ist die Ausgabe?

    versuch mal ein

    throw new OutOfRange(index);

    und

    catch(OutOfRange* e)

    dürfte aber auch nichts bringen

    Dürfte nicht nur nichts bringen, sondern ist sogar gefährlich. Wer soll das mit new angelegte Objekt bitte löschen? Merke: Die Schlüsselwort-Kombination "throw new" ist unter allen Umständen zu meiden.



  • Ja, der Code lässt sich ohne Warnungen compilieren. Nur beim ausführen bricht das Programm, als ob keine Ausnahmebehandlung stattfindet, wegen einer von mir provozierten Zugriffsverletzung ab, halt "out of range".

    Mit dem Debugger habe ich einige Variablen, die von Bedeutung sind, überprüft. An sich ist alles wie es sein soll. Die Exception wird auch ausgeworfen. Warum weiß ich nicht, aber sie wird nicht aufgefangen???

    Obwohl von Z2 abgeraten, habe ich dein Tip ausprobiert. Hat leider auch nichts gebracht.



  • was kommt denn nach dem catch block? denke mal, dass da der fehlerverursacher steckt.



  • Ich bin zwar Anfänger, aber macht

    throw OutOfRange(index);
    

    nicht nen Objekt auf dem Heap?

    und beim Aufruf von

    e.info();
    

    wird der Heap doch (teilweise) überschrieben...

    Nur so ne Idee...



  • @Z2

    btw:

    Warum ist throw new gefährlich?

    Wenn ich eine WasWeissIchFuerNeException* fange,
    ist es doch logisch, dass ich sie per delete lösche, oder?



  • Cpp-Anfänger schrieb:

    Ich bin zwar Anfänger, aber macht

    throw OutOfRange(index);
    

    nicht nen Objekt auf dem Heap?...

    Nö.

    "throw new" ist noch nicht so schlimm .... schlimmer ist das "catch( *)" (das Du damit erzwingst), denn

    Cpp-Anfänger schrieb:

    ...
    Wenn ich eine WasWeissIchFuerNeException* fange,
    ist es doch logisch, dass ich sie per delete lösche, oder?

    und was machst Du, wenn so geschmissen wurde:

    WasWeissIchFuerNeException e;
        throw &e;
    

    ?
    (Kleine Zusatzaufgabe: Was passiert, wenn e im Scope erzeugt wurde, der durch das throw verlassen wird ?)

    Gruß,

    Simon2.



  • Hallo,

    vielen Dank für euere Bemühungen. Leider war das ein Fehlalarm. In einer vorangehenden Funktion, die das Array verwaltet, also vergrössert oder verkleinert, trat ein Speicherleck auf. Darauf wurde ich erst aufmerksam, als ich es unter Linux mit g++ kompilierte und ausführte. Nachdem ich das nun behobe habe funktioniert die Ausnahmebehandlung unter Linux und Windows wie es soll.

    Nur mal zum Verständnis: Ich frage mich nun, ob ich zufällig den Bereich der Klasse OutOfRange überschrieben habe, oder catch() probiert hat diesen Fehler vor dem "OutOfRange" abzufangen?

    Trotzdem, vielen Dank nochmal für eure Unterstützung.

    Grüße



  • Cpp-Anfänger schrieb:

    @Z2

    btw:

    Warum ist throw new gefährlich?

    Wenn ich eine WasWeissIchFuerNeException* fange,
    ist es doch logisch, dass ich sie per delete lösche, oder?

    Nein, ist es nicht. Es könnte ja auch irgendein Zeiger auf etwas anderes sein, das dauerhaft Bestand hat (und somit auf keinen Fall mit delete gelöscht werden darf). Wenn du die Exception fängst, kannst du ja im allgemeinen nicht wissen, wie sie geworfen wurde.

    Und davon abgesehen, willst du dir wirklich solche Stolpersteine antun? So ein delete vergißt man leicht mal, insbesondere an einer solchen Stelle. Besser ist es gleich das ganze so aufzubauen, daß man sich um solche Dinge nicht selbst kümmern muß (zumal die new-Variante keinen nennenswerten Vorteil hat).



  • WasWeissIchFuerNeException e;
        throw &e;
    

    Wenn ich ehrlich bin, ist es (meiner Meinung nach) eher verboten
    als ein catch(Exception* e), denn du kannst doch kein Objekt
    auf dem Stack erzeugen ohne dass das catch davon weiß,
    denn sonst geht dein Objekt kaputt, wenn du irgendwelche
    Daten (z.B. durch Funktionsaufrufe) auf den Stack packst.

    und ne andere Frage:

    Wer wirft schon Objekte, die man nicht zerstören darf?
    Sowas kapselt man doch in ne Exception....



  • Könnte ja zum Beispiel eine statische Instanz sein. Technisch gesehen kein größeres Problem.

    Wer wirft schon Objekte, die man nicht zerstören darf?
    Sowas kapselt man doch in ne Exception....

    Das gilt vielleicht für dich. Das eine scheint mir nicht naheliegender zu sein als das andere Wenn ich soetwas hier:

    catch (exception_typ *Exception)
    

    sehe, wüßte ich jedenfalls ohne die Dokumentation anzusehen nicht, was ich damit machen soll.



  • @Z2:

    Das mit der Dokumentation sehe ich ein...

    Aber ich habe es noch nie gehört, dass man ne statische Instanz wirft...
    Das ist meiner Meinung nach absoluter Quatsch, wenn man das so macht.



  • Cpp-Anfänger schrieb:

    ...Wer wirft schon Objekte, die man nicht zerstören darf?...

    Jeder ! 😉

    Du darfst nicht vergessen, dass geworfene exceptions sich von einem normalen "call-by-value" (oder "-by-pointer") unterscheiden.

    Stephan Dewhurst, "C++ Gotchas", #65
    "...When a throw-expression is executed, the runtime exception-handling-mechanisms copies the exception object to a temporary in a "safe" location. The location of the temporary is highly platform dependent, but the temporary is guaranteed to persist until the exception has been handled. ...
    This is an important property because, ..., when you throw an exception, all hell breaks loose. ... This is why it's not a good idea to throw a pointer....
    The address of the ... object on the heap is copied to a safe location, but the heap memory to which it refers is unprotected.

    .. und Dewhurst ist nur ein beliebiges Beispiel von fitten Leuten, die alle das Werfen eines Heap-Objektes (und das Fangen per Pointer) für eine sehr schlechte Idee halten.
    Letztlich sehe ich auch noch nicht, warum ein Werfen per anonymem Stackobjekt und Fangen per Referenz (so, wie es alle anraten) schlecht sein sollte.

    Gruß,

    Simon2.



  • Simon2 schrieb:

    Letztlich sehe ich auch noch nicht, warum ein Werfen per anonymem Stackobjekt und Fangen per Referenz (so, wie es alle anraten) schlecht sein sollte.

    Das Fangen per Referenz führt dazu, dass das Objekt nicht an die aktuelle Stackposition kopiert wird...

    Ich versuche das mal anzudeuten:

    Rücksprungadresse Funktion mit catch
    ...
    ...
    Exception

    Beim fangen der Exception landest du dann beim catch und
    die Exception wird halt nicht kopiert:

    Rücksprungadresse Funktion mit catch
    (...)
    (...)
    (Exception)

    und wenn du im catch Block jetzt Funktionen aufrufst:

    Rücksprungadresse Funktion mit catch
    ... (neuer Inhalt)
    ... (neuer Inhalt)
    ... (neuer Inhalt)

    und wenn du jetzt auf die Exception zugreifst:

    Rücksprungadresse Funktion mit catch
    ... (neuer Inhalt)
    ... (neuer Inhalt)
    ... (neuer Inhalt) <--- ups, hier sollte doch die Exception stehen --> Absturz

    Das ist glaub ich das Problem...

    Gruß,
    *Cpp-Anfänger*



  • Ich kann deiner Argumentation nicht folgen. Falls das der Punkt war, beim Werfen wird die Exception immer kopiert. Fangen mittels Referenz ist die einzige vernünftige Lösung und die erzeugt definitiv keinerlei Probleme.



  • Stimmt Z2...

    hmm...

    Verwundert mich jetzt aber schon, warum das geht 😮



  • Cpp-Anfänger schrieb:

    ...
    Ich versuche das mal anzudeuten:...

    Sorry,

    ich verstehe überhaupt nichts. Ich vermute, Du hast einfach eine falsche Vorstellung vom Kontrollfluß, wenn eine exception fliegt....

    Mal in Code gesprochen:

    void f() {
        A a;
        if(irgendeine_Bedingung) {
            throw myException(); 
            // ->
            // instantiiert ein myException-Objekt 
            // kopiert es in "safe location"
            //    dazu benutzt es zwar nicht den CpyCtor, 
            //    meckert aber, wenn dieser nicht zugreifbar (private, protected)
            //    ist; ein automatisch erzeugter reicht aber
            // sucht nach passendem exceptionHandler für myException
            // => Wenn es einen findet, ruft es ihn auf; wenn nicht -> terminate();
            //    Der exceptionhandler bekommt den hier aktuellen Stack zur 
            //    Verfügung; er wird also nicht einfach "oben drauf geworfen"
        }
        a.g();
    }
    
    int main(void) {
        try { // installiert exception-handler (*) für myException
            f();
        }
        catch(myException& e) { // (*) Beginn exceptionhandler f. myException
            // Hier wird erstmal der übergebene Callstack "abgeräumt"
            // in diesem Beispiel also Variable a
            // e referenzierzt auf die "safe copy"
            doSomething(e);
            // e (also die safe Kopie) wird abgeräumt
        }
        return 0;
    }
    

    Ich vermute (weiß es aber nicht genau), dass Implementierungen beim try bereits exceptionhandler auf den Stack werfen und beim throw einfach im Stack an die entsprechende Position zurückspringen (so daß der Handler weiß, daß er alles abräumen kann/muß, was sich "über ihm" befindet) ....

    Letztlich bleibt es dabei: Das vorgesehen Konstrukt zum Fangen einer exception ist die Referenz - so hat es sich der Standard gedacht (wenn er anderes Verhalten auch nicht explizit verbietet) und so funktioniert es immer und sicher.

    Gruß,

    Simon2.



  • Cpp-Anfänger schrieb:

    Simon2 schrieb:

    Letztlich sehe ich auch noch nicht, warum ein Werfen per anonymem Stackobjekt und Fangen per Referenz (so, wie es alle anraten) schlecht sein sollte.

    Das Fangen per Referenz führt dazu, dass das Objekt nicht an die aktuelle Stackposition kopiert wird...

    Ich versuche das mal anzudeuten:

    Rücksprungadresse Funktion mit catch
    ...
    ...
    Exception

    Beim fangen der Exception landest du dann beim catch und
    die Exception wird halt nicht kopiert:

    Rücksprungadresse Funktion mit catch
    (...)
    (...)
    (Exception)

    und wenn du im catch Block jetzt Funktionen aufrufst:

    Rücksprungadresse Funktion mit catch
    ... (neuer Inhalt)
    ... (neuer Inhalt)
    ... (neuer Inhalt)

    und wenn du jetzt auf die Exception zugreifst:

    Rücksprungadresse Funktion mit catch
    ... (neuer Inhalt)
    ... (neuer Inhalt)
    ... (neuer Inhalt) <--- ups, hier sollte doch die Exception stehen --> Absturz

    Das ist glaub ich das Problem...

    Gruß,
    *Cpp-Anfänger*

    also bei mir stürzt es nicht ab wenn ich zuerst eine funktion aufrufe und dann auf die per referenz übergebene exception zugreife. Bitte erst ausprobieren dann schreiben.


Log in to reply