Fragen zu C ++ - Ausnahmen beim erneuten Auslösen der ursprünglichen Ausnahme

117

Verursacht das folgende append () im catch, dass die erneut ausgelöste Ausnahme den Effekt des Aufrufs von append () sieht?

try {
  mayThrowMyErr();
} catch (myErr &err) {
  err.append("Add to my message here");
  throw; // Does the rethrow exception reflect the call to append()?
}

Wenn ich es auf diese Weise umschreibe, tritt dann ein Bit-Slicing auf, wenn die eigentliche Ausnahme von myErr abgeleitet wird?

try {
  mayThrowObjectDerivedFromMyErr();
} catch (myErr &err) {
  err.append("Add to my message's base class here");
  throw err; // Do I lose the derived class exception and only get myErr?
}
WilliamKF
quelle

Antworten:

150

In beiden Fällen ändern Sie, da Sie als Referenz abfangen, effektiv den Status des ursprünglichen Ausnahmeobjekts (das Sie sich als an einem magischen Speicherort befindlich vorstellen können, der beim anschließenden Abwickeln gültig bleibt - 0x98e7058im folgenden Beispiel). Jedoch,

  1. Im ersten Fall wird der Aufruf zum Anhängen () wiedergegeben , da Sie mit throw;(das im Gegensatz throw err;zum ursprünglichen Ausnahmeobjekt mit Ihren Änderungen an der "magischen Stelle" an 0x98e7058) erneut wirft)
  2. Im zweiten Fall wird, da Sie etwas explizit werfen, eine Kopie von errerstellt und dann erneut geworfen (an einem anderen "magischen Ort" 0x98e70b0- denn nach allem, was der Compiler weiß, errkönnte sich ein Objekt auf dem Stapel abwickeln, wie es ewar at 0xbfbce430, nicht an der "magischen Stelle" at 0x98e7058), so dass Sie während der Kopierkonstruktion einer Basisklasseninstanz abgeleitete klassenspezifische Daten verlieren .

Einfaches Programm, um zu veranschaulichen, was passiert:

#include <stdio.h>

struct MyErr {
  MyErr() {
    printf("  Base default constructor, this=%p\n", this);
  }
  MyErr(const MyErr& other) {
    printf("  Base copy-constructor, this=%p from that=%p\n", this, &other);
  }
  virtual ~MyErr() {
    printf("  Base destructor, this=%p\n", this);
  }
};

struct MyErrDerived : public MyErr {
  MyErrDerived() {
    printf("  Derived default constructor, this=%p\n", this);
  }
  MyErrDerived(const MyErrDerived& other) {
    printf("  Derived copy-constructor, this=%p from that=%p\n", this, &other);
  }
  virtual ~MyErrDerived() {
    printf("  Derived destructor, this=%p\n", this);
  }
};

int main() {
  try {
    try {
      MyErrDerived e;
      throw e;
    } catch (MyErr& err) {
      printf("A Inner catch, &err=%p\n", &err);
      throw;
    }
  } catch (MyErr& err) {
    printf("A Outer catch, &err=%p\n", &err);
  }
  printf("---\n");
  try {
    try {
      MyErrDerived e;
      throw e;
    } catch (MyErr& err) {
      printf("B Inner catch, &err=%p\n", &err);
      throw err;
    }
  } catch (MyErr& err) {
    printf("B Outer catch, &err=%p\n", &err);
  }
  return 0;
}

Ergebnis:

  Base default constructor, this=0xbfbce430
  Derived default constructor, this=0xbfbce430
  Base default constructor, this=0x98e7058
  Derived copy-constructor, this=0x98e7058 from that=0xbfbce430
  Derived destructor, this=0xbfbce430
  Base destructor, this=0xbfbce430
A Inner catch, &err=0x98e7058
A Outer catch, &err=0x98e7058
  Derived destructor, this=0x98e7058
  Base destructor, this=0x98e7058
---
  Base default constructor, this=0xbfbce430
  Derived default constructor, this=0xbfbce430
  Base default constructor, this=0x98e7058
  Derived copy-constructor, this=0x98e7058 from that=0xbfbce430
  Derived destructor, this=0xbfbce430
  Base destructor, this=0xbfbce430
B Inner catch, &err=0x98e7058
  Base copy-constructor, this=0x98e70b0 from that=0x98e7058
  Derived destructor, this=0x98e7058
  Base destructor, this=0x98e7058
B Outer catch, &err=0x98e70b0
  Base destructor, this=0x98e70b0

Siehe auch:

vladr
quelle
24

Diese Frage ist ziemlich alt und hat eine Antwort, die der Zeit entspricht, zu der sie gestellt wurde. Ich möchte jedoch nur einen Hinweis hinzufügen, wie die ordnungsgemäße Ausnahmebehandlung seit C ++ 11 durchgeführt wird, und ich glaube, dass dies sehr gut dem entspricht, was Sie mit Ihrer Append-Funktion erreichen wollten:

Verwenden Sie std::nested_exceptionundstd::throw_with_nested

In StackOverflow wird hier und hier beschrieben , wie Sie eine Rückverfolgung Ihrer Ausnahmen in Ihrem Code erhalten können, ohne dass ein Debugger oder eine umständliche Protokollierung erforderlich ist, indem Sie einfach einen geeigneten Ausnahmebehandler schreiben, der verschachtelte Ausnahmen erneut auslöst.

Da Sie dies mit jeder abgeleiteten Ausnahmeklasse tun können, können Sie einer solchen Rückverfolgung viele Informationen hinzufügen! Sie können sich auch mein MWE auf GitHub ansehen, wo ein Backtrace ungefähr so ​​aussehen würde:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
GPMueller
quelle
8

Ja, beim erneuten Werfen wird das ursprüngliche Ausnahmeobjekt erneut ausgegeben, das Sie durch eine Referenz geändert haben. Sie können auch eine Basisklassenreferenz abfangen, diese ändern und dennoch den ursprünglich abgeleiteten Ausnahmetyp um erneut werfen throw;.

Tronic
quelle
1

für die erste Frage ja.

aber für die zweite siehe Vlad Antwort. Sie müssen Ihr Ausnahmeobjekt sorgfältig entwerfen, um den Kopiervorgang zu handhaben. Gemäß der Konvention erkennt die Basisklasse ihr untergeordnetes Element nicht, sodass Sie höchstwahrscheinlich die zusätzlichen Daten verlieren, die von der abgeleiteten Klasse übertragen werden.

YeenFei
quelle