Frage

Also der Weg, Ausnahmen in C ++ zu verschachteln mit std::nested_exception is:

void foo() {
  try {
    // code that might throw
    std::ifstream file("nonexistent.file");
    file.exceptions(std::ios_base::failbit);
  }

  catch(...) {
    std::throw_with_nested(std::runtime_error("foo failed"));
  }
}

Diese Technik verwendet jedoch explizite Try / Catch-Blöcke auf jeder Ebene, auf der Ausnahmen verschachtelt werden sollen, was gelinde gesagt hässlich ist.

RAII, der Jon Kalb expandiert da "Verantwortungsübernahme Initialisierung ist", ist dies eine viel sauberere Methode, um mit Ausnahmen umzugehen, anstatt explizite Try / Catch-Blöcke zu verwenden.Bei RAII werden explizite Try / Catch-Blöcke größtenteils nur verwendet, um letztendlich eine Ausnahme zu behandeln, z.um dem Benutzer eine Fehlermeldung anzuzeigen.

Wenn ich mir den obigen Code anschaue, scheint es mir, dass die Eingabe foo() kann als eine Verantwortung angesehen werden, Ausnahmen als zu melden std::runtime_error("foo failed") und verschachteln Sie die Details in einer nested_exception .Wenn wir RAII verwenden können, um diese Verantwortung zu übernehmen, sieht der Code viel sauberer aus:

void foo() {
  Throw_with_nested on_error("foo failed");

  // code that might throw
  std::ifstream file("nonexistent.file");
  file.exceptions(std::ios_base::failbit);
}

Gibt es hier eine Möglichkeit, die RAII-Syntax zu verwenden, um explizite Try / Catch-Blöcke zu ersetzen?


Dazu benötigen wir einen Typ, der beim Aufruf seines Destruktors prüft, ob der Destruktoraufruf auf eine Ausnahme zurückzuführen ist, diese Ausnahme in diesem Fall verschachtelt und die neue, verschachtelte Ausnahme auslöst, sodass das Abwickeln normal fortgesetzt wird.Das könnte so aussehen:

struct Throw_with_nested {
  const char *msg;

  Throw_with_nested(const char *error_message) : msg(error_message) {}

  ~Throw_with_nested() {
    if (std::uncaught_exception()) {
      std::throw_with_nested(std::runtime_error(msg));
    }
  }
};

Jedoch std::throw_with_nested() erfordert, dass eine 'aktuell behandelte Ausnahme' aktiv ist, was bedeutet, dass sie nur im Kontext eines catch-Blocks funktioniert.Also würden wir so etwas brauchen wie:

  ~Throw_with_nested() {
    if (std::uncaught_exception()) {
      try {
        rethrow_uncaught_exception();
      }
      catch(...) {
        std::throw_with_nested(std::runtime_error(msg));
      }
    }
  }

Leider gibt es meines Wissens nichts Vergleichbares rethrow_uncaught_excpetion() definiert in C++.

War es hilfreich?

Lösung

In Ermangelung einer Methode zum Abfangen (und Konsumieren) der nicht abgefangenen Ausnahme im Destruktor gibt es keine Möglichkeit, eine verschachtelte oder nicht verschachtelte Ausnahme im Kontext des Destruktors erneut auszulösen, ohne std::terminate aufgerufen wird (wenn die Ausnahme im Kontext der Ausnahmebehandlung ausgelöst wird).

std::current_exception (kombiniert mit std::rethrow_exception) gibt nur einen Zeiger auf eine aktuell behandelte Ausnahme zurück.Dies schließt seine Verwendung in diesem Szenario aus, da die Ausnahme in diesem Fall explizit nicht behandelt wird.

In Anbetracht dessen ist die einzige Antwort aus ästhetischer Sicht zu geben.Try-Blöcke auf Funktionsebene lassen dies etwas weniger hässlich aussehen.(passen sie für ihre stilpräferenz):

void foo() try {
  // code that might throw
  std::ifstream file("nonexistent.file");
  file.exceptions(std::ios_base::failbit);
}
catch(...) {
  std::throw_with_nested(std::runtime_error("foo failed"));
}

Andere Tipps

Mit RAII ist das unmöglich

In Anbetracht der einfachen Regel

Destruktoren dürfen niemals werfen.

mit RAII ist es unmöglich, das zu implementieren, was Sie wollen.Die Regel hat einen einfachen Grund:Wenn ein Destruktor beim Abwickeln des Stapels aufgrund einer Ausnahme im Flug eine Ausnahme auslöst, dann terminate() wird aufgerufen und Ihre Bewerbung ist tot.

Alternative

In C ++ 11 können Sie mit Lambdas arbeiten, die das Leben etwas erleichtern können.Du kannst schreiben

void foo()
{
    giveErrorContextOnFailure( "foo failed", [&]
    {
        // code that might throw
        std::ifstream file("nonexistent.file");
        file.exceptions(std::ios_base::failbit);
    } );
}

wenn Sie die Funktion implementieren giveErrorContextOnFailure auf folgende Weise:

template <typename F>
auto giveErrorContextOnFailure( const char * msg, F && f ) -> decltype(f())
{
    try { return f(); }
    catch { std::throw_with_nested(std::runtime_error(msg)); }
}

Dies hat mehrere Vorteile:

  • Sie kapseln ein, wie der Fehler verschachtelt ist.
  • Das Ändern der Art und Weise, wie Fehler verschachtelt werden, kann für das gesamte Programm geändert werden, wenn diese Technik strikt programmweit befolgt wird.
  • Die Fehlermeldung kann wie in RAII vor den Code geschrieben werden.Diese Technik kann auch für verschachtelte Bereiche verwendet werden.
  • Es gibt weniger Code-Wiederholungen:Du musst nicht schreiben try, catch, std::throw_with_nested und std::runtime_error.Dies macht Ihren Code leichter wartbar.Wenn Sie das Verhalten Ihres Programms ändern möchten, müssen Sie Ihren Code nur an einer Stelle ändern.
  • Der Rückgabetyp wird automatisch abgeleitet.Also wenn deine Funktion foo() sollte etwas zurückgeben, dann fügen Sie einfach hinzu return vor giveErrorContextOnFailure in deiner Funktion foo() .

Im Release-Modus gibt es normalerweise kein Performance-Panel im Vergleich zur Try-Catch-Methode, da Vorlagen standardmäßig inline sind.

Eine weitere interessante Regel zu befolgen:

Nicht verwenden std::uncaught_exception().

Es gibt eine schöne artikel darüber thema von Herb Sutter, das diese Regel perfekt erklärt.Kurz:Wenn Sie eine Funktion haben f() welches heißt aus einem Destruktor während des Stapelabwickelns sieht so aus

void f()
{
    RAII r;
    bla();
}

wo der Zerstörer von RAII sieht aus wie

RAII::~RAII()
{
    if ( std::uncaught_exception() )
    {
        // ...
    }
    else
    {
        // ...
    }
}

dann wird immer der erste Zweig im Destruktor genommen, da im äußeren Destruktor beim Stapelabwickeln std::uncaught_exception() gibt immer true zurück, auch innerhalb von Funktionen, die von diesem Destruktor aufgerufen werden, einschließlich des Destruktors von RAII.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top