2009-07-07 4 views
8

Ich habe gerade einen sehr subtilen Fehler in unserem Code behoben, der durch das Schneiden einer Ausnahme verursacht wurde, und ich möchte jetzt sicherstellen, dass ich genau verstehe, was passiert ist.Exception Slicing - liegt das an generiertem Copy-Konstruktor?

Hier ist unsere Basis Ausnahmeklasse, eine abgeleitete Klasse, und relevante Funktionen:

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

    <snip - get/set routines and print function> 

protected: 
private: 
    int mCode;    // thrower sets this 
    char mMessage[Exception::MessageLen]; // thrower says this FIXME: use String 
}; 

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

void innercall { 
    <do stuff> 
    throw Derived("Bad things happened!"); 
} 

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

Der Fehler war natürlich, dass outercall endet eine Ausnahme zu werfen, statt eines abgeleitet. Mein Fehler resultierte aus höheren Call-Stack-Versuchen, den Abgeleiteten Fehler zu fangen.

Jetzt möchte ich nur sicherstellen, dass ich verstehe - ich glaube, dass in der "throw e" -Zeile ein neues Exception-Objekt erstellt wird, mit einem Standard-Copy-Konstruktor. Geht das wirklich so?

Wenn ja, darf ich Kopierkonstruktoren für Objekte ausschließen, die geworfen werden? Ich würde wirklich bevorzugen, dass dies nicht wieder passiert, und unser Code hat keinen Grund, Exception-Objekte (die ich kenne) zu kopieren.

Bitte keine Kommentare zu der Tatsache, dass wir unsere eigene Ausnahme-Hierarchie haben. Das ist ein bisschen altes Design, an dem ich arbeite, um es zu korrigieren (ich mache gute Fortschritte. Ich habe die Klasse der selbstgewachsenen Strings und viele der selbst gezogenen Container los.)

UPDATE: Um es klar zu sagen, ich hatte den Fehler behoben (indem ich 'Wurf e' in 'Wurf' änderte), bevor ich die Frage überhaupt stellte. Ich wollte nur bestätigen, was vor sich geht.

+2

Ich weiß, dass du gesagt hast, dass du dir über die anderen Sachen keine Sorgen machen musst, sondern nur zwei andere Dinge: mach deine 'Exception' Klasse von' std :: exception' und erhalte Dinge durch 'const &'. – GManNickG

+0

Was hat den Vorteil, als const zu fangen? Ich fange immer durch Verweis, aber warum const? (Nicht, dass ich das im Moment mit dieser Klasse machen könnte, aber eines Tages ...) –

+1

@Kohne: Der Vorteil von const in dieser Einstellung ist ähnlich wie in jeder anderen Einstellung - du spezifizierst klar die Absicht, die du nicht hast t beabsichtigen, den Zustand des Objekts zu ändern, und dass der Compiler sicherstellen sollte, dass die unveränderlichen Objekte einfacher zu verstehen sind (dh debuggen, testen, Code korrekt prüfen), da sie beim Schreiben referentiell helfen transparenter Code –

Antwort

19

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

Ich glaube, das ist Teil des Standards, aber ich habe keine Kopie zu verweisen.

Der Typ der Ausnahme, die im catch-Block ausgelöst wird, ist der Basistyp des Caches und nicht der Typ des Objekts, das geworfen wurde. Der Weg um dieses Problem ist zu throw; eher als throw e;, die die ursprüngliche gefangene Ausnahme werfen wird.

+0

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

10

A quick google schlägt vor, dass ja, Sie werfen den Kopierkonstruktor ist erforderlich und muss öffentlich sein. (Das macht Sinn, da Sie eine Kopie e initialisiert und zu werfen.)

Wie dem auch sei, benutzen Sie einfach throw ohne die Ausnahme-Objekt angibt, erneut auslösen, was in der catch gefangen wurde. Sollte das nicht das Problem lösen?

catch(Exception& e) 
    { 
    printf("Exception seen here! %s %d\n", __FILE__, __LINE__); 
    throw; 
    } 
+0

Es tut mir leid, ja, ich habe diesen Bug bereits behoben, indem ich den Retrow korrekt ausführe. Ich habe versucht, eine Bestätigung über den Kopierkonstruktor zu erhalten. –

6

Ja.

throw e; 

löst eine Ausnahme von der statischen Art von e, unabhängig davon, was auch immer e tatsächlich ist. In diesem Fall wird die Ausnahme Derived mit einem Kopierkonstruktor in eine Exception kopiert.

In diesem Fall können Sie einfach

throw; 

richtig Derived Ausnahme sprudeln zu erhalten.

Wenn Sie in anderen Fällen polymorphes Werfen interessant finden, beziehen Sie sich auf die always so useful C++ FAQ Lite.

1

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

Das Ausnahmeobjekt wird zuerst in ein temporäres kopiert und Sie sollten throw verwenden. Um es mit dem Standard-15,1/3:

ein Einwurf Expression ein temporäres Objekt initialisiert, das Ausnahmeobjekt genannt, die Art der davon durch Entfernen aller Top-Level-cv-Qualifizierer vom statischen Typ des Operanden bestimmt von throw und Einstellung des Typs von "Array von T" oder "Funktion, die T zurückgibt" zu "Zeiger zu T" bzw. "Zeiger zu Funktion, die T zurückgibt".

Ich denke, dies zu einer sehr nützlichen Codierung Standardregel gibt:

Basisklassen eine Ausnahmehierarchie einen rein virtuellen destructor haben sollten.

oder

Das Copy-Konstruktor für eine Basisklasse in einer Ausnahmehierarchie wird geschützt.

Entweder erreicht das Ziel, dass der Compiler warnen, wenn Sie versuchen, „e werfen“, da im ersten Fall können Sie keine Instanz einer abstrakten Klasse und die zweite erstellen, da Sie nicht die Kopie Konstruktor aufrufen können.