2009-07-07 4 views
8

Acabo de corregir un error muy sutil en nuestro código, causado por el corte de una excepción, y ahora quiero asegurarme de que entiendo exactamente lo que estaba sucediendo.División de excepciones: ¿esto se debe al constructor de copia generado?

Aquí está nuestra clase de excepción base, una clase derivada, y las funciones relevantes:

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

El error fue, por supuesto que outercall termina por lanzar una excepción, en lugar de un derivado. Mi error fue el resultado de una mayor cantidad de intentos de pila de llamadas para detectar el error Derivado.

Ahora, solo quiero asegurarme de que entiendo: creo que en la línea 'throw e', se está creando un nuevo objeto Exception, utilizando un constructor de copia predeterminado. ¿Eso es lo que realmente está pasando?

Si es así, ¿puedo bloquear los constructores de copia para los objetos que se lanzarán? Realmente preferiría que esto no vuelva a suceder, y nuestro código no tiene ninguna razón para copiar objetos de excepción (que yo sepa).

Por favor, no hay comentarios sobre el hecho de que tenemos nuestra propia jerarquía de excepciones. Es un viejo diseño que estoy trabajando para corregir (estoy progresando bastante. Me he deshecho de la clase de cuerda doméstica y de muchos de los contenedores de fabricación propia).

ACTUALIZACIÓN: Para que quede claro, arreglé el error (cambiando 'throw e' a 'throw') antes de hacer la pregunta. Estaba buscando confirmación de lo que estaba pasando.

+2

Sé que dijiste que no te preocupes por las otras cosas, pero solo dos cosas más: haz que tu clase 'Exception' herede de' std :: exception' y atrapa cosas por 'const &'. – GManNickG

+0

¿Cuál es la ventaja de atrapar como const? Siempre capto por referencia, pero ¿por qué const? (No es que pueda hacer eso con esta clase en este momento de todos modos, pero un día ...) –

+1

@Kohne: La ventaja de const en este entorno es similar a la de cualquier otro entorno: especifica claramente la intención de que no lo haga Tengo la intención de cambiar el estado del objeto y que el compilador debe asegurarse de que no. La idea detrás de esto es que los objetos inmutables son más fáciles de razonar (es decir, depurar, probar, probar el código correcto) ya que ayudan a escribir referencialmente código transparente –

Respuesta

19

Cuando arroja un objeto, en realidad está tirando una copia del objeto, no del original. Piénselo: el objeto original está en la pila, pero la pila se está desenrollando e invalidando.

Creo que esto es parte del estándar, pero no tengo una copia para referencia.

El tipo de excepción lanzada en el bloque catch es el tipo de base del catch, no el tipo de objeto que se arrojó. La forma de solucionar este problema es throw; en lugar de throw e; que arrojará la excepción original capturada.

+0

Eso indicaría que no puedo lanzar algo que no tiene un constructor de copia, entonces? Eso tiene sentido. –

10

A quick google sugiere que sí, que está lanzando el constructor de copia es obligatorio y debe ser público. (Lo cual tiene sentido, ya que estás inicializar una copia de e y lanzando eso.)

De todos modos, sólo tiene que utilizar throw sin especificar el objeto de excepción, para volver a lanzar lo que estaba atrapado en el catch. ¿No debería eso resolver el problema?

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

Lo siento, sí, ya arreglé ese error haciendo el re-lanzamiento correctamente. Intentaba obtener una confirmación sobre el constructor de copias. –

6

Sí.

throw e; 

lanza una excepción de tipo estático de e, independientemente de lo que realmente es e. En este caso, la excepción Derived se copia en un Exception utilizando un constructor de copia.

En este caso puede simplemente

throw; 

para obtener la burbuja Derived excepción correctamente.

Si te interesan los lanzamientos polimórficos en algunos otros casos, consulta always so useful C++ FAQ Lite.

1

C++ nunca deja de sorprenderme. Hubiera perdido una gran cantidad de dinero si esta hubiera sido una apuesta sobre cuál era el comportamiento.

El objeto de excepción se copia primero en un temporal y debería haber usado throw. Para citar el estándar 15.1/3:

Un saque expresión inicializa un objeto temporal, llamado el objeto de excepción, el tipo de la que se determina mediante la eliminación de cualquier CV-calificadores de nivel superior del tipo estático del operando de lanzar y ajustar el tipo de "matriz de T" o "función que devuelve T" a "puntero a T" o "puntero a función que devuelve T", respectivamente.

creo que esto da lugar a una regla estándar de codificación de gran utilidad:

Las clases base de una jerarquía de excepciones deben tener un destructor virtual puro.

o

El constructor de copia para una clase base en una jerarquía de excepciones debe ser protegido contra.

De cualquier logra el objetivo de que el compilador le advertirá cuando intenta "tirar e" ya que en el primer caso no se puede crear una instancia de una clase abstracta y el segundo porque no se puede llamar al constructor de copia.

Cuestiones relacionadas