2009-10-22 16 views
17

Ho cercato di creare alcune classi di eccezioni personalizzate per una libreria C++ su cui sto lavorando. Queste eccezioni personalizzate acquisiscono informazioni extra, come file, numero di riga, ecc., Necessarie per il debug, se per qualche motivo durante il test un'eccezione non viene catturata nel posto giusto. Tuttavia la maggior parte delle persone sembra raccomandare di ereditare dalla classe std :: exception in STL, che sono d'accordo, ma mi chiedevo forse sarebbe meglio usare ereditarietà multiple per ereditare da ciascuno degli derived std::exception classes (ad esempio std :: runtime_error) e una classe di eccezioni personalizzate, come nel codice qui sotto?Eccezioni personalizzate in C++

Un'altra cosa, come si fa a copiare i costruttori di copia e gli operatori di assegnazione nelle classi di eccezioni? Dovrebbero essere disabilitati?

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

Cosa vorresti ottenere da questo? Perché non usare l'ereditarietà singola? Perché è necessario personalizzare * entrambe le eccezioni * Eccezione e RuntimeError? – jalf

+0

Volevo utilizzare la funzionalità della mia classe Exception personalizzata e mantenere la gerarchia delle eccezioni STL ereditando dalle varie classi derivate std :: exception. In questo modo un problema con: try { throw RunTimeException (...); } catch (std :: runtime_error & e) { ... } catch (std :: exception & e) {...} prenderebbe anche la mia classe personalizzata. Potrebbe essere una perdita di tempo però. Potrei anche solo provare { lanciare RunTimeException (...); } catch (std :: exception & e) {...} ma potrebbe rendere più difficile identificare l'eccezione. Non so quale sia la pratica tipica quando le classi di eccezioni personalizzate? – Tom

+0

Direi solo ereditare da std :: runtime_error (o std :: exception). Se vuoi un trattamento specifico, puoi avere più catture dopo tutto. –

risposta

11

Si dovrebbe cercare boost::exception

Lo scopo di Boost eccezione è quella di facilitare la progettazione di classe di eccezione gerarchie e per aiutare a scrivere gestione delle eccezioni e segnalazione degli errori codice.

Supporta il trasporto di dati arbitrari al sito cattura, che è altrimenti difficile a causa delle non-tiro requisiti (15.5.1) per eccezione tipi. I dati possono essere aggiunti a qualsiasi oggetto di eccezione , direttamente in l'espressione di lancio (15.1) o in un propagatore dello stack di chiamate.

La possibilità di aggiungere i dati ad eccezione oggetti dopo che sono stati passati al tiro è importante, perché spesso alcuni delle informazioni necessarie per gestire un'eccezione non è disponibile nel contesto in cui viene rilevato il guasto.

Boost Eccezione supporta anche N2179 stile copia d'eccezione oggetti, implementato non intrusivo e automaticamente dalla funzione boost :: throw_exception.

+0

Non ho mai usato boost prima. Lo esaminerò. Grazie per il post. – Tom

+1

Libreria davvero fantastica. Ora vorrei averlo letto prima! –

17

mi chiedevo forse sarebbe meglio usare l'ereditarietà multipla di ereditare da ciascuna delle std :: derivato classi di eccezioni

Si noti che questo è un problema, a causa del fatto che le eccezioni nella libreria standard derivano non virtualmente l'una dall'altra. Se si introduce l'ereditarietà multipla, si ottiene la gerarchia dell'eccezione diamante senza eredità virtuale e non sarà possibile rilevare le eccezioni derivate da std::exception&, poiché la classe di eccezione derivata trasporta due sottooggetti di std::exception, rendendo std::exception una "classe di base ambigua".

Esempio concreto:

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

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

ora my_runtime_error deriva (indirettamente) da std::exception due volte, una per std::run_time_error e una volta attraverso my_exception.Dal momento che l'ex non deriva da std::exception praticamente, questo

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

non funzionerà.

Edit:

Penso che ho visto il primo esempio di una gerarchia di eccezioni di classe che coinvolge MI in uno dei libri di Stroustrup, così ho concluso che, in generale, è una buona idea. Che le eccezioni della std lib non derivino virtualmente l'una dall'altra che considero un fallimento.

Quando ho progettato per l'ultima volta una gerarchia di eccezioni, ho usato MI molto estesamente, ma non derivava dalle classi di eccezioni lib std. In quella gerarchia c'erano classi di eccezioni astratte che hai definito in modo che i tuoi utenti potessero catturarle e le corrispondenti classi di implementazione, derivate da queste classi astratte e da una classe di base di implementazione, che dovresti effettivamente lanciare. Per facilitare questa operazione, ho definito alcuni modelli, che avrebbe fatto tutto il lavoro duro:

// 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 
          ); 
} 

Guardando indietro a questo momento, penso che avrei potuto fare che exc_impl modello di classe derivano da std::exception (o qualsiasi altra classe nella std lib eccezione gerarchia, passata come parametro modello opzionale), poiché non deriva mai da un'altra istanza exc_impl. Ma all'epoca non era necessario, quindi non mi è mai venuto in mente.

+0

+1: abbastanza esplicativo – fmuecke

+0

Grazie per il post sbi. Sarebbe una buona pratica creare classi di eccezioni con eredità multipla? O è meglio solo ereditare da std :: exception class? – Tom

+0

@ Tom: ho aggiunto i miei pensieri come una modifica. – sbi

Problemi correlati