9 Stimmen

Ausnahme-Slicing - liegt das an einem generierten Kopierkonstruktor?

Ich habe gerade einen sehr subtilen Fehler in unserem Code behoben, der durch das Slice einer Ausnahme verursacht wurde, und jetzt möchte ich sicherstellen, dass ich genau verstehe, was passiert ist.

Hier ist unsere Basisklassen-Ausnahme, eine abgeleitete Klasse und relevante Funktionen:

class Exception
{
public:
  // Konstruktion
  Exception(int code, const char* format="", ...);
  virtual ~Exception(void);

protected:
private:
  int mCode;                // werfende Stelle setzt das
  char mMessage[Exception::MessageLen]; // werfende Stelle sagt das FIXME: Verwende String
};

class Derived : public Exception {
public:
  Derived (const char* werfendeStelleSagte) : Exception(1, werfendeStelleSagte) {};
};

void innercall {

  throw Derived("Schlimme Dinge sind passiert!");
}

void outercall {
  try {
    innercall();
  }
  catch(Exception& e)
  {
    printf("Ausnahme hier gesehen! %s %d\n", __FILE__, __LINE__);
    throw e;
  }
}

Der Fehler bestand natürlich darin, dass outercall am Ende eine Ausnahme wirft, anstatt eine abgeleitete Ausnahme. Mein Fehler resultierte daraus, dass weiter oben im Aufrufstapel der Versuch, die abgeleitete Ausnahme zu fangen, fehlschlug.

Jetzt möchte ich nur sicherstellen, dass ich verstanden habe - Ich glaube, dass an der Stelle 'throw e' ein neues Exception-Objekt erstellt wird, das einen Standard-Kopierkonstruktor verwendet. Ist das wirklich, was passiert?

Wenn ja, darf ich die Kopierkonstruktoren für Objekte, die geworfen werden sollen, sperren? Ich würde wirklich bevorzugen, dass dies nicht noch einmal passiert, und unser Code hat keinen Grund, Exception-Objekte zu kopieren (soweit ich weiß).

Bitte keine Kommentare dazu, dass wir unsere eigene Ausnahmehierarchie haben. Das ist etwas alter Design, an dem ich arbeite, um es zu korrigieren (Ich mache gute Fortschritte. Ich habe die selbstgemachte String-Klasse beseitigt und viele der selbstgemachten Container).

UPDATE: Um es klarzustellen, ich hatte den Fehler behoben (indem ich 'throw e' in 'throw' änderte), bevor ich überhaupt die Frage gestellt habe. Ich wollte nur eine Bestätigung dessen, was passiert ist.

4 Stimmen

Ich weiß, du hast gesagt, dass du dir keine Sorgen um den anderen Kram machen sollst, aber nur zwei weitere Dinge: Lass deine Exception-Klasse von std::exception erben und fange Dinge mit const & ab.

0 Stimmen

Was ist der Vorteil von const beim Fangen? Ich fange immer per Referenz, aber warum const? (nicht dass ich das im Moment sowieso mit dieser Klasse machen kann, aber eines Tages...)

3 Stimmen

@Kohne: Der Vorteil von const in diesem Fall ist ähnlich wie in jedem anderen Fall - Sie geben deutlich an, dass Sie nicht beabsichtigen, den Zustand des Objekts zu ändern, und dass der Compiler sicherstellen sollte, dass Sie dies nicht tun - die Idee hinter const ist, dass unveränderliche Objekte einfacher zu handhaben sind (d.h. Debuggen, Testen, den Code als korrekt nachweisen), da sie beim Schreiben von referenziell transparentem Code helfen.

24voto

Mark Ransom Punkte 283960

Wenn Sie ein Objekt werfen, werfen Sie tatsächlich eine Kopie des Objekts, nicht das Original. Denken Sie darüber nach - das Originalobjekt befindet sich im Stapel, aber der Stapel wird abgewickelt und ungültig gemacht.

Ich glaube, das ist Teil des Standards, aber ich habe keine Kopie zur Referenz.

Die Art der Ausnahme, die im Catch-Block geworfen wird, ist der Basistyp des Catch, nicht der Typ des geworfenen Objekts. Der Weg um dieses Problem ist, throw; anstatt throw e; zu werfen, was die ursprünglich gefangene Ausnahme wirft.

1 Stimmen

Das würde bedeuten, dass ich etwas nicht werfen kann, das keinen Kopierkonstruktor hat, oder? Das macht Sinn.

12voto

jalf Punkte 235501

Eine schnelle Google-Suche deutet darauf hin, dass ja, der Kopierkonstruktor erforderlich ist und öffentlich sein muss. (Was Sinn macht, da Sie eine Kopie von e initialisieren und diese werfen.)

Wie auch immer, verwenden Sie einfach throw ohne das Ausnahmeobjekt anzugeben, um das wiederzuwerfen, was im catch gefangen wurde. Sollte das nicht das Problem lösen?

  catch(Exception& e)
  {
    printf("Exception hier gesehen! %s %d\n", __FILE__, __LINE__);
    throw;
  }

0 Stimmen

Ich entschuldige mich, ja, ich habe diesen Fehler bereits behoben, indem ich das erneute Werfen ordnungsgemäß gemacht habe. Ich habe versucht, Bestätigung über den Kopierkonstruktor zu bekommen.

8voto

laalto Punkte 143902

Ja.

throw e;

wirft eine Ausnahme des statischen Typs von e, unabhängig davon, was e tatsächlich ist. In diesem Fall wird die Derived-Ausnahme mithilfe eines Kopierkonstruktors in eine Exception kopiert.

In diesem Fall können Sie einfach

throw;

verwenden, um die Derived-Ausnahme korrekt nach oben weiterzugeben.

Wenn Sie sich für polymorphes Werfen in anderen Fällen interessieren, verweisen Sie auf das immer so nützliche C++ FAQ Lite.

1voto

Richard Corden Punkte 20939

C++ hört nie auf, mich zu verblüffen. Ich hätte viel Geld verloren, wenn das eine Wette über das Verhalten gewesen wäre!

Das Ausnahmen-Objekt wird zuerst in ein temporäres Objekt kopiert und du solltest throw benutzt haben. Um den Standard 15.1/3 zu zitieren:

Ein Wurfausdruck initialisiert ein temporäres Objekt, das Ausnahmenobjekt genannt wird, dessen Typ festgelegt wird, indem alle Top-Level-cv-Qualifikatoren vom statischen Typ des Operanden von throw entfernt werden und der Typ von "Array von T" oder "Funktion, die T zurückgibt", zu "Zeiger auf T" oder "Zeiger auf eine Funktion, die T zurückgibt", entsprechend angepasst wird.

Ich denke, das führt zu einer sehr nützlichen Codierungsregel:

Basisklassen einer Ausnahme-Hierarchie sollten einen rein virtuellen Destruktor haben.

oder

Der Kopierkonstruktor für eine Basisklasse in einer Ausnahme-Hierarchie sollte geschützt sein.

Beides erreicht das Ziel, dass der Compiler eine Warnung ausgibt, wenn du versuchst, "throw e" zu verwenden, da im ersten Fall keine Instanz einer abstrakten Klasse erstellt werden kann und im zweiten Fall der Kopierkonstruktor nicht aufgerufen werden kann.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X