eine vertretbare ausnahme beim werfen einer exception aus einem destructor



  • hi leute,

    waere es eine vertretbare ausnahme wenn ich eine klasse folgend definieren wuerde und aus dem destructor eine exception werfe?

    1. die klasse darf nur einen POD type als member haben.
    2. sie darf keine basisklasse haben
    3. sie muss final sein
    4. sie darf keine resourcen allokieren
    5. sie muss std:is_standard_layout sein
    6. sie muss temporaer sein
    7. Sie besitzt keinen copy-ctor, noch move-ctor, noch operator=(&) noch operator=(&&)

    im code wuerde das so aussehen (vereinfacht):

    class throw_bad_alloc
    {
       public:
          throw_bad_alloc(void) noexcept : m_value(0) { }
          throw_bad_alloc(const throw_bad_alloc&) = delete;
          ~throw_bad_alloc(void) noexcept(false)
          { if(m_value != 0) throw std::bad_alloc; }
    
          auto operator=(const throw_bad_alloc&) -> throw_bad_alloc& = delete;
    
          auto prepare_to_throw(void) const noexcept -> void { m_value = 1; }     
    
       private:
          mutable unsigned int m_value;
    };
    
    // so wirds angewendet
    
    auto __stdcall call_function_in_dll(const char *str, const throw_bad_alloc &a = throw_bad_alloc()) -> int;
    

    die funktion call_function_in_dll ist in einer DLL datei die ich aufrufe. wenn jetzt nicht genug speicher reserviert werden konnte, wird die member funktion prepare_to_throw vom object a aufgerufen.
    wenn der aufruf der funktion zurueckgekehrt ist, wird beim zerstoeren von a eine exception geworfen, falls prepare_to_throw aufgerufen worden war.
    mir faellt sonst einfach keine moeglichkeit ein, wie ich automatisch nach dem aufruf bei bedarf eine exception werfen kann.

    ich weiß das man laut standard keine exception aus dem destructor werfen soll/darf da es sonst zu undefined behavior kommen kann und das die STL das verbietet. aber es wird einfach nur als temporaerer parameter fuer (member-)funktionsaufrufe verwendet. bin ich da auf der sicheren seite oder faellt jemanden was besseres ein?

    Meep Meep



  • da es sonst zu undefined behavior kommen kann

    Nein, das ist definiert: std::terminate wird aufgerufen



  • manni66 schrieb:

    da es sonst zu undefined behavior kommen kann

    Nein, das ist definiert: std::terminate wird aufgerufen

    wann wird std::terminate aufgerufen?


  • Mod



  • SeppJ schrieb:

    http://en.cppreference.com/w/cpp/error/terminate

    Hier also vor allem der Punkt 2.

    Allgemein zum Thema:
    http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI11_FR.HTM

    aber doch nicht wenn ich den destructor mit noexcept(false) angegeben habe, oder?
    ich hatte es versaeumt oben in die liste explicit reinzuschreiben, aber der destructor ist im beispiel noexcept(false).

    VS2017 ruft da auf jeden fall nicht std::terminate auf.

    Meep Meep


  • Mod

    Wer redet denn von noexcept? Punkt 2 ist:

    std::terminate() is called by the C++ runtime when exception handling fails for any of the following reasons:
    [...]
    2) an exception is thrown during exception handling (e.g. from a destructor of some local object, or from a function that had to be called during exception handling)
    [...]

    Und der Artikel und die davon weiter verlinkten Artikel erklären dann nochmal, wieso das nicht nur technisch falsch ist, sondern wieso das auch logisch keinen Sinn macht, eine Exception in einem Destruktor zu werfen.

    Wenn du es exakt so wie hier gezeigt machst, werden die beschriebenen Gefahren niemals zutreffen. Aber dieser Code wird dir bei der kleinsten Änderung explodieren.


  • Mod

    std::terminate() is called by the C++ runtime when exception handling fails for any of the following reasons:
    [...]
    2) an exception is thrown during exception handling (e.g. from a destructor of some local object, or from a function that had to be called during exception handling)
    [...]

    Ja, das ist sehr unsauber geschrieben und entsprechend fehlerhaft. Ich bin ein bisschen verwundert, dass diese Formulierung offenbar viele Revisionen überlebt hat.

    1. Kein Fehler, aber in einer Referenz sollte besser darauf hingewiesen werden:
    Exception handling i.S.d. Standards meint all die Magie, die das Programm ausführt, wenn ein throw-Ausdruck ausgewertet wird, bis zu dem Zeitpunkt, an dem die Ausführung eines Exception handlers beginnt. Wärend beim normalen Programmieren Exception handling sich typischerweise mit den Exception Handlern selbst beschäftigt. Das ist aus meiner sicht eine etwas unglückliche Terminologie des Standards, dort allerdings widerspruchsfrei.
    2. "an exception is thrown during exception handling"
    Diese Formulierung ist zu umfänglich. Zunächst ist zu bemerken, dass der Punkt in der Referenz zwei Anstriche im Standard zusammenzufassen versucht: dort wird nämlich einerseits allgemein von Funktionen gesprochen, die vom Exception handling mechanism (direkt) aufgerufen werden (typischerweise Copy-ctors um Exceptionsobjekte zu kopieren; aber auch andere Konstruktoren/Konvertiertungsoperatoren/Destruktoren sind theoretisch möglich, wenn entsprechend der üblichen copy-Initialisierungsregeln etwas entsprechendes aufzurufen ist oder ggf. Defaultargumente involviert sind), und andererseits der Zerstörung von Objekten während des Stackunwindings (einscließlich solcher Objekte, deren Konstruktion durch das Werfen der Exception unterbrochen wurde) - gemeint sind hier also nat. Destruktoraufrufe, die unmittelbar vom Stackunwindingmechanismus aufgerufen werden, als auch (im Prinzip) Deallokationsfunktionen, falls ein new-Ausdruck im Konstuktor fehlschlägt. Wobei die speziellere Regel in 3.7.4.2/3 klar macht, dass das Verlassen einer Deallokationsfunktion per Exception tatsächlich immer zu UB führt). Der exotische Fall, dass der Aufruf einer Deallokationsfunktion Argumente umfasst, deren Initialierung zu einer Exception führt, dürfte allerdings zu terminate() führen.
    Wesentlich ist hierbei, dass terminate nur dann aufgerufen wird, wenn diese Funktionen, die direkt vom Exceptionhandlingmechanismus aufgerufen wurden, durch eine Exception beendet werden. Es es ist kein Problem, weitere Exceptions innerhalb dieser Funktionen zu erzeugen, solange diese zusätzlichen Exceptions diese direkt aufgerufenen Funktionen nicht verlassen. In diesem Fall ist es auch möglich, dass std::uncaught_exceptions() einen Wert größer als 1 liefert.
    3. destructor of some local object
    local hat hier nicht so recht etwas zu suchen. "local" ist eine Eigenschaft von Namen und würde sich in diesem Zusammenhang folglich nur auf Variablen (=Objekte und Referenzen, die einen Namen haben) beziehen. Der Exceptionhandlingmechanismus zerstört schließlich auch temporäre Objekte und kümmert sich zudem auch um Objekte mit dynamischer Speicherdauer, deren Erzeugung fehlschlägt. Lokale Variablen mit statischer Lebensdauer werden hingegen nicht angefasst.
    4. Historische Feinheit am Rande:
    In C++98/03 begann das Exception Handling bereits mit der Auswertung des throw Ausdruckes. Bereits die Erzeugung eines Exceptionobjektes konnte zu einem std::terminate() Aufruf führen, falls das Kopieren fehlschlug. Weshalb etwa Exceptionobjekte, die std::string Member haben, im Prinzip etwas problematisch waren. Ab C++11 kommt es nur noch zu std::terminate() Aufrufen, falls die zweite Exception geworfen wurde, nachdem das erste Exceptionobjekt erfolgreich erzeugt wurde. Zu Problemen mit dem Kopieren kann es hier nur noch kommen, falls der catch-Parameter keine Referenz ist - was i.Allg. ohnehin vermieden werden sollte.


Anmelden zum Antworten