2009-10-22 10 views
17

Ich habe versucht, einige benutzerdefinierte Ausnahmeklassen für eine C++ - Bibliothek, an der ich arbeite, zu erstellen. Diese benutzerdefinierten Ausnahmen erfassen zusätzliche Informationen wie Datei, Zeilennummer usw., die zum Debuggen benötigt werden, wenn beim Testen ausnahmsweise nicht an der richtigen Stelle gefangen wird. Die meisten Leute scheinen jedoch zu empfehlen, von der Klasse std :: exception in der STL zu vererben, was ich stimme, aber ich frage mich, ob es vielleicht besser wäre, die mehrfache Vererbung von jedem der derived std::exception classes zu übernehmen (zB std :: runtime_error)) und eine benutzerdefinierte Ausnahmeklasse, wie im folgenden Code?Benutzerdefinierte Ausnahmen in C++

Eine andere Sache, wie geht man über Kopierkonstruktoren und Zuweisungsoperatoren in Ausnahmeklassen? Sollten sie behindert sein?

class Exception{ 
    public: 
     explicit Exception(const char *origin, const char *file, 
          const int line, const char *reason="", 
          const int errno=0) throw(); 

     virtual ~Exception() throw(); 

     virtual const char* PrintException(void) throw(); 

     virtual int GetErrno(void); 

    protected: 
     std::string m_origin; 
     std::string m_file; 
     int m_line; 
     std::string m_reason; 
     int m_errno; 
}; 

class RuntimeError: public virtual std::runtime_error, public Exception{ 
    public: 
       explicit RuntimeError(const char *origin, const char *file, 
            const int line, const char *reason="", 
            const int errno=0) throw(); 
     virtual ~RuntimeError() throw(); 
}; 
+0

Was würden Sie davon gewinnen? Warum nicht einzelne Vererbung verwenden? Warum müssen Sie * sowohl * Exception als auch RuntimeError Exceptions anpassen? – jalf

+0

Ich wollte die Funktionalität meiner benutzerdefinierten Exception-Klasse verwenden und die STL-Ausnahmehierarchie beibehalten, indem ich von den verschiedenen abgeleiteten std :: exception-Klassen erbte. Auf diese Weise ein Haken mit: versuchen { werfen RunTimeException (...); } catch (std :: runtime_error & e) { ... } fangen (std :: Ausnahme & e) {...} würde auch meine benutzerdefinierte Klasse fangen. Es könnte jedoch eine Zeitverschwendung sein. Ich könnte auch nur versuchen { throw RunTimeException (...); } catch (std :: exception & e) {...} aber es könnte es schwieriger machen, die Ausnahme dann zu identifizieren. Ich weiß nicht, was die typische Praxis bei benutzerdefinierten Ausnahmeklassen ist? – Tom

+0

Ich würde sagen, nur erben von Std :: Runtime_error (oder Std :: Ausnahme). Wenn Sie eine bestimmte Behandlung wünschen, können Sie doch mehrere Fänge haben. –

Antwort

11

sollten Sie versuchen, boost::exception

Der Zweck der Boost-Ausnahme ist die Gestaltung von Ausnahmeklasse erleichtern Hierarchien und Code Reporting Ausnahmebehandlung und Fehler schreiben zu helfen.

Es unterstützt den Transport von beliebigen Daten an die Fangstelle, die aufgrund der nicht-throw Anforderungen (15.5.1) zur Ausnahme ansonsten heikel ist Typen. Daten können zu jedem beliebigen Exception-Objekt hinzugefügt werden, entweder direkt in dem throw-Ausdruck (15.1), oder zu einem späteren Zeitpunkt , während das Exception-Objekt den Call-Stack weiterleitet.

Die Fähigkeit, Daten zu Ausnahme Objekte hinzufügen, nachdem sie zu throw bestanden hat, ist wichtig, weil oft einig der Informationen eine Ausnahme im Kontext ist nicht verfügbar zu handhaben benötigt, wo die Fehler erkannt wird.

Boost-Exception unterstützt auch N2179-Stil Kopieren von Ausnahme Objekte, implementiert nicht-intrusiv und automatisch von der boost :: throw_exception Funktion.

+0

Ich habe noch nie Boost verwendet. Ich werde es mir ansehen. Danke für die Post. – Tom

+1

Ziemlich tolle Bibliothek. Jetzt wünschte ich, ich hätte es vorher gelesen! –

17

Ich habe mich gefragt, vielleicht wäre es besser, Mehrfachvererbung zu verwenden, die von jedem des abgeleiteten std :: Ausnahmeklassen

Hinweis zu erben, dass dies ein Problem ist, aufgrund der Tatsache, dass Die Ausnahmen in der Standardbibliothek leiten sich nicht virtuell voneinander ab. Wenn Sie die Mehrfachvererbung einführen, erhalten Sie die gefürchtete Diamantenausnahmehierarchie ohne virtuelle Vererbung und können keine abgeleiteten Ausnahmen von std::exception& abfangen, da Ihre abgeleitete Ausnahmeklasse zwei Unterobjekte von std::exception trägt, was std::exception zu einer "mehrdeutigen Basisklasse" macht.

Konkretes Beispiel:

class my_exception : virtual public std::exception { 
    // ... 
}; 

class my_runtime_error : virtual public my_exception 
         , virtual public std::runtime_error { 
    // ... 
}; 

Jetzt my_runtime_error herleitet (indirekt) von std::exception zweimal, einmal durch std::run_time_error und einmal durch my_exception.Da der ehemalige leitet sich nicht von std::exception praktisch, diese

try { 
    throw my_runtime_error(/*...*/); 
} catch(const std::exception& x) { 
    // ... 
} 

wird nicht funktionieren.

Edit:

Ich glaube, ich habe das erste Beispiel einer Ausnahmeklassenhierarchie denen MI in einem der Stroustrup Bücher gesehen, also habe ich festgestellt, dass im Allgemeinen ist es eine gute Idee ist. Dass die Ausnahmen der std lib nicht virtuell voneinander abstammen, halte ich für einen Misserfolg.

Als ich zuletzt eine Ausnahmehierarchie entworfen habe, habe ich MI sehr ausgiebig verwendet, aber nicht von den Ausnahmeklassen der std lib abgeleitet. In dieser Hierarchie gab es abstrakte Ausnahmeklassen, die Sie so definierten, dass Ihre Benutzer sie abfangen konnten, und entsprechende Implementierungsklassen, die von diesen abstrakten Klassen und von einer Implementierungsklasse stammten, die Sie tatsächlich werfen würden. Um dieses zu erleichtern, definiert ich einige Vorlagen, die all die harte Arbeit tun würde:

// something.h 
class some_class { 
private: 
    DEFINE_TAG(my_error1); // these basically define empty structs that are needed to 
    DEFINE_TAG(my_error2); // distinguish otherwise identical instances of the exception 
    DEFINE_TAG(my_error3); // templates from each other (see below) 
public: 
    typedef exc_interface<my_error1> exc_my_error1; 
    typedef exc_interface<my_error2> exc_my_error2; 
    typedef exc_interface<my_error3,my_error2> // derives from the latter 
            exc_my_error3; 

    some_class(int i); 
    // ... 
}; 

//something.cpp 
namespace { 
    typedef exc_impl<exc_my_error1> exc_impl_my_error1; 
    typedef exc_impl<exc_my_error2> exc_impl_my_error2; 
    typedef exc_impl<exc_my_error3> exc_impl_my_error3; 
    typedef exc_impl<exc_my_error1,exc_my_error2> // implements both 
            exc_impl_my_error12; 
} 
some_class::some_class(int i) 
{ 
    if(i < 0) 
    throw exc_impl_my_error3(EXC_INFO // passes '__FILE__', '__LINE__' etc. 
          , /* ... */ // more info on error 
          ); 
} 

Mit Blick auf diese jetzt zurück, ich glaube, ich hätte gemacht, dass exc_impl Klassenvorlage aus std::exception ableiten (oder jede andere Klasse in der std lib exception hierarchy, übergeben als optionaler Template-Parameter), da sie niemals von einer anderen exc_impl Instanz abgeleitet wird. Aber damals war das nicht nötig, also kam es mir nie in den Sinn.

+0

+1: durchaus erklärend – fmuecke

+0

Danke für den Beitrag sbi. Würden Ausnahmeklassen mit Mehrfachvererbung eine gute Übung sein? Oder ist es besser, nur von std :: ausnahmeklasse zu erben? – Tom

+0

@Tom: Ich habe meine Gedanken als Bearbeitung hinzugefügt. – sbi

Verwandte Themen