2010-06-02 17 views
6

Tengo una API que internamente tiene algunas excepciones para el informe de errores. La estructura básica es que tiene un objeto de excepción raíz que hereda de std::exception, luego arrojará alguna subclase de eso.forma correcta de almacenar una excepción en una variable

Dado que capturar una excepción arrojada en una biblioteca o hilo y atraparla en otra puede llevar a un comportamiento indefinido (al menos Qt se queja de ello y lo desautoriza en muchos contextos). Me gustaría envolver las llamadas de biblioteca en funciones que devolverán un código de estado y, si se produjo una excepción, una copia del objeto de excepción.

¿Cuál es la mejor manera de almacenar una excepción (con su comportamiento polimórfico) para su uso posterior? Creo que la futura API C++ 0x utiliza algo como esto. Entonces, ¿cuál es el mejor enfoque?

Lo mejor que puedo pensar es tener un método clone() en cada clase de excepción que devolverá un puntero a una excepción del mismo tipo. Pero eso no es muy genérico y no trata las excepciones estándar en absoluto.

¿Alguna idea?

EDIT: Parece que C++ 0x tendrá a mechanism for this. Se describe como "magia de biblioteca". ¿Eso significa que no requiere ninguna de las funciones de lenguaje de C++ 0x? si no, ¿hay implementaciones que sean compatibles con C++ 03?

EDIT: Parece que boost tiene un implementation of exception copying. Mantendré la pregunta abierta para cualquier respuesta que no sea boost::copy_exception.

EDITAR: Para responder a las preocupaciones de j_random_hacker sobre la causa de la excepción de un error de falta de memoria. Para esta biblioteca en particular y conjunto de excepciones, este no es el caso. Todas las excepciones derivadas del objeto de excepción raíz representan diferentes tipos de errores de análisis causados ​​por una entrada de usuario no válida. Las excepciones relacionadas con la memoria simplemente provocarán el lanzamiento de un std::bad_alloc, que se trata por separado.

Respuesta

4

Tienes lo que creo que es tu mejor respuesta. No puede mantener una referencia a la excepción original porque va a dejar el alcance. Simplemente tiene que hacer una copia y la única forma genérica de hacerlo es con una función prototipo como clone().

Lo siento.

+0

Pero tenga en cuenta que la causa raíz de la excepción puede ser una condición de falta de memoria, en cuyo caso 'clone()' fallará si intenta llamar a 'new'. Esto se puede conseguir, por ejemplo al sobrecargar 'operator new()' para cada tipo de excepción para asignar desde un búfer estático, pero tampoco necesariamente es bonito. –

+0

@j_random_hacker: Bueno, dado que nos estamos volviendo pedantes, lo que usted llama falta de memoria se conoce mejor como mal-alloc y no tiene que ser real de memoria. Otra razón para el mal-alloc es la solicitud de demasiada memoria, y en ese caso las siguientes pequeñas asignaciones tendrán éxito – sbk

+1

@sbk: ¿Es realmente pedante considerar la posibilidad de quedarse sin memoria? –

1

Puede arrojar cualquier cosa, incluso punteros. Siempre se puede hacer algo como esto:

throw new MyException(args); 

Y luego en el gestor de excepciones almacenar el puntero atrapado, que será totalmente polimórfico (continuación suponiendo que MyException deriva de std::exception):

try { 

    doSomething(); // Might throw MyException* 

} catch (std::exception* pEx) { 

    // store pEx pointer 
} 

Sólo debe tener cuidado con las pérdidas de memoria cuando lo hace de esta manera, que es la razón por la cual se utiliza normalmente el valor por producto y captura por referencia.

Más sobre captura-por-puntos: http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8

+1

El problema con esta solución, como yo lo veo, es que podría estar lanzando una excepción debido a algo que tiene como causa raíz una condición de falta de memoria, en cuyo caso 'nuevo' fallará. –

+1

Eso se menciona en el enlace, pero en teoría podría solucionarlo teniendo una excepción de memoria fuera de memoria preasignada o estática que arroje en ese caso. Pero entonces necesitaría asegurarse de no eliminar esto como lo haría con sus otras excepciones. Es factible, pero hay que tener cuidado con muchas trampas como esta. –

+0

¿No podríamos verificar si la excepción indica una condición de falta de memoria y simplemente configurar un int/boolean para indicar esto? –

0

La razón por la captura de una excepción lanzada en una biblioteca y la captura de él en otro puede conducir a un comportamiento indefinido es que estas bibliotecas podrían estar vinculados con diferentes bibliotecas de tiempo de ejecución. Si devuelve la excepción de una función en lugar de tirarla, no evitará ese problema.

+0

Creo que tiene razón, creo que el problema que tiene Qt con él es más sobre pasar entre hilos (también he agregado esa sutileza a mi pregunta). –

0

Mi biblioteca de utilidades tiene una clase AnyException que es básicamente la misma que boost::any sin el soporte de fundición. En cambio, tiene un miembro Throw() que arroja el objeto original almacenado.

struct AnyException { 
    template<typename E> 
    AnyException(const E& e) 
    : instance(new Exception<E>(e)) 
    { } 

    void Throw() const { 
    instance->Throw(); 
    } 

private: 
    struct ExceptionBase { 
    virtual void Throw() const =0; 
    virtual ~ExceptionBase() { } 
    }; 

    template<typename E> 
    struct Exception : ExceptionBase { 
    Exception(const E& e) 
     : instance(e) 
    { } 

    void Throw() const { 
     throw std::move(instance); 
    } 

    private: 
    E instance; 
    }; 
    ExceptionBase* instance; 
}; 

esto es una simplificación, pero eso es el marco básico. Mi código actual desactiva la copia, y tiene semántica de movimiento en su lugar. Si es necesario, puede agregar un método Clone virtual al ExceptionBase con la suficiente facilidad ... ya que Exception conoce el tipo original del objeto, puede reenviar la solicitud al constructor de copia real e inmediatamente admite todos los tipos copiables, no solo los que tienen su propio método Clone.

Cuando se diseñó, no fue para almacenar excepciones detectadas ... una vez que se lanzaba una excepción, se propagaba de manera normal, por lo que no se tenían en cuenta las condiciones de falta de memoria. Sin embargo, imagino que podría agregar una instancia de std::bad_alloc al objeto y almacenarla directamente en esas situaciones.

struct AnyException { 
    template<typename E> 
    AnyException(const E& e) { 
     try { 
      instance.excep = new Exception<E>(e); 
      has_exception = true; 
     } catch(std::bad_alloc& bad) { 
      instance.bad_alloc = bad; 
      bas_exception = false; 
     } 
    } 

    //for the case where we are given a bad_alloc to begin with... no point in even trying 
    AnyException(const std::bad_alloc& bad) { 
    instance.bad_alloc = bad; 
    has_exception = false; 
    } 

    void Throw() const { 
    if(has_exception) 
     instance.excep->Throw(); 
    throw instance.bad_alloc; 
    } 

private: 
    union { 
    ExceptionBase* excep; 
    std::bad_alloc bad_alloc; 
    } instance; 
    bool has_exception; 
}; 

que en realidad no han probado que el segundo bit en absoluto ... puede ser que falte algo salta a la vista que va a impedir que funcione.

Cuestiones relacionadas