2009-07-21 17 views
17

Después de pasar a través de algunos enlaces en el manejo de excepciones (1, 2 y 3), sé que los programas en C++ puede lanzar casi cualquier cosa como excepciones (int, char*, string, exception clase). Sé que std::exception es la clase base para las excepciones estándar lanzadas por el programa. Sin embargo, yo estoy tratando de diseñar un try ... catch bloque como tal:¿Qué tipo de excepción debo arrojar?

try 
{ 
    MyFunc(); 
} 
catch (certain exceptions) 
{ 
    // deal with the exception accordingly 
} 
catch (the rest of the exceptions) 
{ 
    // deal with these accordingly 
} 

mientras MyFunc() contiene lo siguiente:

void MyFunc() 
{ 
    ... 
    if (certain condition is true) throw exception; 
    ... 
} 

El problema es que en esa parte del MyFunc función, me No estoy seguro de qué tipo de excepción debería arrojar. Para mantener el código limpio implementando mi propia clase de excepciones, no tengo idea de cuál sería una buena forma de implementar dicha clase de excepciones.

Respuesta

18

Obtendría su propia clase de std::exception, de modo que hay alguna manera de manejar las excepciones de manera uniforme.

Si esto parece excesivo, puede lanzar std::logic_error o uno de los otros tipos de excepción estándar destinados a las aplicaciones.

También podría utilizarlos como clases base para sus propias excepciones más específicas: esto ahorra un poco de trabajo porque se encargan de implementar el método what para usted.

Tenga en cuenta que las jerarquías de excepciones profundas pueden ser inutilizables, porque básicamente está adivinando por adelantado cómo categorizar los errores y sus clientes pueden estar en desacuerdo.

1

Si puede utilizar el impulso, entonces debe hacerlo. Consulte this link sobre cómo usar las excepciones de impulso. También puede diseñar su propia jerarquía de clases de excepción según lo indicado por otras respuestas, pero debe ocuparse de aspectos sutiles como los requisitos de 'nothrow' del método 'qué'. Un diseño básico de cómo se puede hacer esto en las líneas de impulso :: excepción se explica a continuación: -

#include <string> 
#include <memory> 
#include <stdexcept> 

/************************************************************************/ 
/* The exception hierarchy is devised into 2 basic layers. 
    System exceptions and Logic exceptions. But Logic exceptions are 
    convertible to the ultimate base 'System' in the system layer. 
*************************************************************************/ 

// the system exception layer 
    namespace ExH 
    { 
    namespace System { 
     // This is the only way to make predefined exceptions like 
     // std::bad_alloc, etc to appear in the right place of the hierarchy. 
     typedef std::exception Exception; 
     // we extend the base exception class for polymorphic throw 
     class BaseException : public Exception { 
     public: 
     BaseException() throw() {} 
     explicit BaseException(char const* /*desc*/) throw() 
      : Exception() 
     {} 
     BaseException(BaseException const& that) 
      : Exception(that) 
     {} 
     virtual void raise() const { throw *this; } // used to throw polymorphically 
     virtual ~BaseException() throw() {} 
     }; 
     // module level classes compose and catch the descriptive 
     // versions of layer-exceptions 
     class DescriptiveException : public BaseException { 
     public: 
     explicit DescriptiveException (char const* description) throw() 
      : description_(description) 
     { } 
     explicit DescriptiveException (std::string const& description) throw() 
      : description_(description.c_str()) 
     { } 

     virtual ~DescriptiveException() throw() {} 

     DescriptiveException (DescriptiveException const& src) throw() 
      : BaseException(src) 
     { 
      this->description_ = src.description_; 
     } 
     DescriptiveException& operator= (DescriptiveException const& src) throw() 
     { 
      if (this != &src) 
      { 
       this->description_ = src.description_; 
      } 
      return *this; 
     } 

     /*virtual*/ char const* what() const throw() { return description_; } 
     /*virtual*/ void raise() const // used to throw polymorphically 
     { throw *this; } 
     protected: 
     DescriptiveException() throw(); 
     private: 
     char const* description_; 
     }; 

    } 
    } 

// the logic exception layer 
    namespace ExH 
    { 
    namespace Logic 
    { 

     // Logic::Exception inherits from System::Exception for the 
     // following reason. Semantically for some part of the 
     // system particular instance of Logic::Exception may seem as 
     // opaque System::Exception and the only way to handle it would 
     // be to propagate it further. In other words Logic::Exception 
     // can be seamlessly "converted" to System::Exception if there is 
     // no part of the system interested in handling it. 
     // 
     class BaseException : public System::BaseException 
     { 
     public: 
     BaseException() throw() {} 
     explicit BaseException(char const* desc) throw() 
      : System::BaseException(desc) 
     {} 
     BaseException(BaseException const& that) 
      : System::BaseException(that) 
     {} 
     virtual void raise() const { throw *this; } // used to throw polymorphically 
     virtual ~BaseException() throw() {} 
     }; 
     // module level classes compose and catch the descriptive 
     // versions of layer-exceptions 
     class DescriptiveException : public BaseException { 
     public: 
     explicit 
     DescriptiveException (char const* description) throw() 
      : description_(new std::string(description)) 
     { } 
     explicit 
     DescriptiveException (std::string const& description) throw() 
      : description_(new std::string(description)) 
     { } 
     DescriptiveException(DescriptiveException const& src) throw() 
      : BaseException(src) 
     { 
      // copy the string 
      std::string* str = new std::string(src.description_.get()->c_str()); 
      description_.reset(str); 
     } 

     virtual ~DescriptiveException() throw() {} 
     /*virtual*/ char const* what() const throw() { return description_->c_str(); } 
     /*virtual*/ void raise() const { throw *this; } 
     private: 
     DescriptiveException& operator= (DescriptiveException const& src) throw(); // copy disabled 
     std::auto_ptr<std::string> description_; // do not use std::string, as it can throw 
     }; 
    } 
    } 


/************************************************************************/ 
/* Users of the exception hierarchy compose specific exceptions as and 
when needed. But they can always be caught at the System::Exception base 
class level. Some of the standard conversion examples are demonstrated :- 

class MyClass { 
public: 
    class Exception_ {}; 
    typedef 
    Compound <Exception_, Logic::DescriptiveException> 
    Exception; 

    class InvalidArgument_ {}; 
    typedef 
    Compound <InvalidArgument_, Exception> 
    InvalidArgument; 

    class NotInitialized_ {}; 
    typedef 
    Compound <NotInitialized_, Exception> 
    NotInitialized; 
public: 
    void myFunction1() const throw(NotInitialized); 
    void myFunctionN() const throw(NotInitialized); 
}; 

void MyClass::myFunction1() const { 
    throw NotInitialized("Not Inited!"); 
} 

void MyClass::myFunctionN() const { 
    try { 
    // call myFunction1() 
    } 
    catch(NotInitialized const& e){ 
    // use e 
    } 
} 

This has to be per-class basis. The exposed module will have an exception 
specification which will catch all the sub-class exceptions. The calling 
module will in turn rely on this exception-specification. This will allow 
us to have generalized exception-catching at the application-level and 
more specialized exception-catching at the specific module level.  */ 
/************************************************************************/ 

// a simple template to compose the exceptions as per conversion requirements 
    namespace ExH 
    { 
    template <typename Type, typename Base> 
    class Compound : public Base 
    { 
    public: 
     explicit Compound (char const* description) throw() 
     : Base(description) 
     {} 
     explicit Compound (std::string const& description) throw() 
     : Base(description) 
     {} 

     Compound (Compound const& src) throw() 
     : Base(src) 
     {} 

     virtual ~Compound() throw() {} 
    protected: 
     Compound() throw() {} 
    private: 
     Compound& operator= (Compound const& src) throw(); // disable copy 
    }; 

    } 
+0

haría con impulso ser una buena idea a pesar de toda mi ISN proyecto ¿Todavía no usas bibliotecas de impulso? – stanigator

+0

Probablemente no. La jerarquía anterior puede usarse en su lugar y modificarse según sus necesidades. He tenido un éxito moderado usando dichos niveles de eexception en grandes proyectos. Además, puede haber problemas menores de portabilidad si está utilizando la compilación cruzada usando boost ya que está muy templado. Algunos compiladores pueden no cumplir con el estándar C++. – Abhay

4

pensé que podría ser interesante para publicar algo de código real para un cambio. Esta es la clase de excepción de mi propia biblioteca de utilidad utiliza:

//--------------------------------------------------------------------------- 
// a_except.h 
// 
// alib exception handling stuff 
// 
// Copyright (C) 2008 Neil Butterworth 
//--------------------------------------------------------------------------- 

#ifndef INC_A_EXCEPT_H 
#define INC_A_EXCEPT_H 

#include "a_base.h" 
#include <exception> 
#include <sstream> 

namespace ALib { 

//------------------------------------------------------------------------ 
// The only exception thrown directly by alib 
//------------------------------------------------------------------------ 

class Exception : public std::exception { 

    public: 

     Exception(const std::string & msg = ""); 
     Exception(const std::string & msg, int line, 
         const std::string & file); 

     ~Exception() throw(); 

     const char *what() const throw(); 
     const std::string & Msg() const; 

     int Line() const; 
     const std::string & File() const; 

    private: 

     std::string mMsg, mFile; 
     int mLine; 
}; 

//------------------------------------------------------------------------ 
// Macro to throw an alib exception with message formatting. 
// Remember macro is not in ALib namespace! 
//------------------------------------------------------------------------ 

#define ATHROW(msg)            \ 
{                 \ 
    std::ostringstream os;           \ 
    os << msg;              \ 
    throw ALib::Exception(os.str(), __LINE__, __FILE__);   \ 
}                 \ 


} // namespace 

#endif 

Y este es el archivo .cpp:

//--------------------------------------------------------------------------- 
// a_except.h 
// 
// alib exception handling stuff 
// 
// Copyright (C) 2008 Neil Butterworth 
//--------------------------------------------------------------------------- 

#include "a_except.h" 
using std::string; 

namespace ALib { 

//--------------------------------------------------------------------------- 
// exception with optional message, filename & line number 
//------------------------------------------------------------------------ 

Exception :: Exception(const string & msg) 
    : mMsg(msg), mFile(""), mLine(0) { 
} 

Exception :: Exception(const string & msg, int line, const string & file) 
    : mMsg(msg), mFile(file), mLine(line) { 
} 

//--------------------------------------------------------------------------- 
// Do nothing 
//--------------------------------------------------------------------------- 

Exception :: ~Exception() throw() { 
} 

//------------------------------------------------------------------------ 
// message as C string via standard what() function 
//------------------------------------------------------------------------ 

const char * Exception :: what() const throw() { 
    return mMsg.c_str(); 
} 

//------------------------------------------------------------------------ 
// as above, but as C++ string 
//------------------------------------------------------------------------ 

const string & Exception :: Msg() const { 
    return mMsg; 
} 

//--------------------------------------------------------------------------- 
// File name & line number 
//--------------------------------------------------------------------------- 

int Exception :: Line() const { 
    return mLine; 
} 

const string & Exception :: File() const { 
    return mFile; 
} 

} // namespace 

// end 
+0

Su primer ctor de excepciones debería ser 'explicit' y' ATHROW (x) 'estaría mejor definido para' do {std :: stringstream _s; _s << (x); arrojar ALib :: Exception (_s.str(), __FILE__, __LINE__)} while (0) 'para evitar problemas de precedencia del operador y permitir el uso de' ATRHOW (x) 'como una declaración correcta de C/C++. –

+0

Considero su punto sobre explícito. Llamar a stringstream _s sería una mala idea, ya que el nombre está reservado en el ámbito del espacio de nombres. Y a diferencia de otros, nunca he encontrado la manera de construir macros que valga la pena, ya que nunca he tenido problemas sin eso, probablemente debido a mis otras prácticas de programación. –

+2

¡Si se hubiera derivado de std :: runtime_error algo de esto ya se manejaría para usted! –

12

Aquí es un fragmento de código que muestra cómo extender y utilizar el std :: excepción clase: (Por cierto, este código tiene un error, que explicaré más adelante).

#include <iostream> 
#include <string> 
#include <exception> 

class my_exception : public std::exception 
{ 
public: 
    explicit my_exception(const std::string& msg) 
     : msg_(msg) 
    {} 

    virtual ~my_exception() throw() {} 

    virtual const char* what() const throw() 
    { 
     return msg_.c_str(); 
    } 

private: 
    std::string msg_; 
}; 

void my_func() throw (my_exception&) 
{ 
    throw my_exception("aaarrrgggg..."); 
} 

int 
main() 
{ 
    try 
    { 
     my_func(); 
    } 
    catch (my_exception& ex) 
    { 
     std::cout << ex.what() << '\n'; 
    } 
    return 0; 
} 

Tenga en cuenta que el constructor es explícita y el destructor y lo que() se declaran (usando tiro()) para indicar que ellos mismos no van a lanzar excepciones. Aquí es donde está el error. ¿Se garantiza que la llamada a msg_.c_str() no generará excepciones propias? ¿Qué pasa con el constructor de cadenas que estamos utilizando para inicializar msg_? También puede generar excepciones. ¿Cómo podemos diseñar una clase de excepción que esté a salvo de las excepciones planteadas por los objetos miembros? La respuesta es: heredar de std :: runtime_error o una subclase std :: exception similar.Así que la forma correcta de aplicar my_exception sería:

class my_exception : public std::runtime_error 
{ 
public: 
    my_exception(const std::string& msg) 
     : std::runtime_error(msg) 
    { } 
}; 

No tenemos que anular lo que(), como ya se implementa en std :: runtime_error. El manejo correcto del búfer de mensajes lo realiza std :: runtime_error, por lo que podemos estar seguros de que my_exception no generará ningún error desconocido en el tiempo de ejecución.

0

Normalmente debe derivar sus propias clases de excepción de std :: exception y sus derivadas para representar errores relevantes para su dominio de aplicación, por ejemplo, si trata con archivos, debe tener FileNotFoundException que incluye la ruta de archivo y otra información relevante, esto De forma que puede crear bloques catch para manejar ciertos tipos de errores y dejar de lado otros, también debe abstenerse de lanzar o atrapar excepciones que no sean de clase.

Tener un vistazo a las jerarquías de excepciones similares en .NET y Java para ver cómo modelar errores generales (archivos de errores, errores IO, errores de red, etc.)

Cuestiones relacionadas