2010-02-04 11 views
5

He buscado SO para obtener una respuesta a esto, pero no he encontrado ninguna.C++ Ramificaciones de ignorar la excepción del constructor

Cuando un objeto arroja una excepción al final del constructor, ¿el objeto es válido o es uno de esos "depende de la técnica de construcción"?

Ejemplo:

struct Fraction 
    { 
     int m_numerator; 
     int m_denominator; 
     Fraction (double value, 
       int denominator); 
    }; 
    Fraction::Fraction(double value, int denominator) 
    : m_numerator(0), m_denominator(denominator) 
    { 
     if (denominator == 0) 
     { 
/* E1 */  throw std::logic_error("Denominator is zero."); 
     } 
     m_numerator = static_cast<int>(value * static_cast<double>(denominator)); 
     double actual_value = 0.0; 
     actual_value = static_cast<double>(m_numerator)/static_cast<double>(m_denominator); 
     double error = fabs(actual_value - value); 
     if (error > 5.0E-5) 
     { 
/* E2 */ throw std::logic_error("Can't represent value in exact fraction with given denominator"); 
     } 
    } 

El programa:

int main(void) 
{ 
    try 
    { 
     Fraction f1(3.14159264, 4); // Throws exception, E2 above. 
    } 
    catch (...) 
    { 
     cerr << "Fraction f1 not exactly representable as fraction with denom. of 4.\n"; 
    } 

    // At this point, can I still use f1, knowing that it is an approximate fraction? 

    return EXIT_SUCCESS; 
} 

En este ejemplo, puede ser utilizado F1 después se detecta la excepción, sabiendo que es un valor aproximado?

Los miembros de datos se han construido e inicializado.

No veo ninguna regla de lenguaje C++ violada por lo anterior.

Editar: Se ha cambiado el valor delta de error de 5.0E05 a 5.0E-5.

+2

¿Realmente puede acceder a f1 detrás de su alcance? – nusi

+0

Entonces, el desafío es cómo usar un objeto después de que * falló * en la construcción. Tal vez este sea un buen tema para SO wiki. –

+0

** Absolutamente imposible. ** Cuando se lanza una excepción, se propagará hacia afuera. Esto significa que deja el alcance actual, comprueba si se puede atrapar, y si no, se repite. Cuando un constructor lanza una excepción, el * muy * primer ámbito que se deja es el que crea el objeto, cada vez. Ya sea porque está directamente en un bloque try/catch o porque necesita abandonar el alcance para buscar uno. – GManNickG

Respuesta

4

La respuesta de Jonathan es correcta. Además, aunque la fracción puede estar en un estado válido, no recomendaría el uso de excepciones para control de flujo, y especialmente para la comunicación sobre el estado de un objeto. En su lugar, considere agregar algún tipo de is_exactly_representable a su API de objeto Fraction que devuelve bool.

+0

Buena sugerencia sobre el 'is_exactly_representable'. El objeto es válido, pero no completamente exacto. Similar a cómo 1/3 no se puede representar exactamente en coma flotante. –

5

f1 está fuera del alcance después de que se captura la excepción. Ya no existe, por lo que no puedes usarlo.

+0

¿Tengo la certeza de que la gramática del lenguaje impide el uso de un objeto después de que el constructor falla debido al problema del alcance? No veo cómo atrapar la excepción sin un bloque 'try' /' catch' y la construcción en el bloque 'try'. –

+0

@Thomas: Correcto, no se puede capturar una excepción sin un bloque catch. Si no hubieras tenido uno, habría dejado 'main' y el sistema operativo lo habría atrapado en su lugar. (De este modo, le ofrecemos esos bonitos cuadros de diálogo de bloqueo). – GManNickG

+0

@Thomas Matthews El idioma impide que utilice cualquier variable que haya sido declarada dentro del alcance del intento fuera del intento, ya que estaría fuera del alcance. Por lo tanto, ni siquiera puede intentar usar dicha variable fuera del bloque try. La única forma de evitarlo sería utilizar punteros y, dado que el objeto es un estado no válido, debe eliminarse, no utilizarse. –

1

Estoy de acuerdo con fbrereto.

Si arroja un error en un constructor que es equivalente a decir "la construcción de este objeto no funcionó" o "no se pudo crear el objeto" y como tal, debe manejar ese hecho, yo solo Haga esto para los errores fatales para los cuales el objeto no se puede usar de otra manera, como no se pudo abrir un archivo que esperábamos poder abrir en la clase MySettingsReader.

+0

+1 Aunque esta no es la solución que elijo, le doy +1 para aclarar cuándo usar excepciones. –

2

No, una vez que el alcance que se define f1 en las salidas, ya no puede usar el objeto. Por lo tanto, en el código:

try 
{ 
    Fraction f1(3.14159264, 4); // Throws exception, E2 above. 

    // f1 can be used until here 
} 
catch (...) 
{ 
} 

// The scope that f1 was defined in is over, so the compiler will not let 
// you reference f1 

Dicho esto, tal vez debería replantearse lanzar una excepción cuando no se puede representar el valor real . Debido a que sólo puede ser aplicable para ciertos usos, que podría requerir la persona que llama para que lo solicite:

enum FractionOption { disallowInexact, allowInexact }; 

Fraction::Fraction(double value, int denominator, 
        FractionOption option = disallowInexact) 
{ 
    ... 
    if ((option == disallowInexact) && (error > 5.0E-5)) 
    { 
     throw std::logic_error("Can't represent value ..."); 
    } 
} 

Fraction f1(3.14159264, 4, allowInexact); 
+0

+1, me gusta la política agregada al constructor. Eso no pasó por mi mente. {Pensé que las políticas solo se usaban con plantillas, como en * Diseño moderno en C++ * de Andrei Alexandrescu.} –

0

Si un objeto se produce una excepción durante la construcción no invalida el objeto técnico. En su ejemplo, f1 queda fuera del alcance y, por lo tanto, se desasigna cuando se lanza la excepción.

Si f1 era un puntero que se asignaron y construyeron dentro del bloque try, y el constructor (no el asignador) arrojó una excepción, su puntero sería a la memoria asignada válida. Si el objeto en esa memoria contenía o no datos válidos dependería de tu constructor; Básicamente, si los datos eran válidos antes del lanzamiento, serían válidos después del lanzamiento.

También parece que lo que está intentando hacer no es un uso apropiado para las excepciones, y cuestionaría su diseño aquí.Lanzar una excepción en una llamada de constructor generalmente significa que el objeto no se construyó correctamente y no se debe usar.

+2

"su puntero sería a la memoria asignada válida": esto es incorrecto. –

+0

Las asignaciones se traducen aproximadamente de: 'foo * f = 0; f = new foo() 'to:' foo * f = 0; void * __memory = 0; foo * __dummy = 0; pruebe {__memory = :: operator new (sizeof (f)); __dummy = new (__memory) foo(); } catch (...) {:: operator delete (__ memory); throw;} f = __dummy; 'Y sí, soy consciente de que se ve asqueroso. : P Pero un puntero permanece sin cambios si 'nuevo' lanza; y la memoria está libre. – GManNickG

1

siguiente JMD

es entonces f1 disponibles en la cláusula catch. La respuesta es también no. ASÍ MISMO, ves que las reglas de alcance te impiden incluso hacer esta pregunta en el código.

La única cosa que le daría a distancia que el objeto existiera sería si su destructor pasó - pero no se ejecuta si el contructor no completó

2

tiro en el constructor de construcción = no -> objeto inutilizable

Como ya se mencionó, si se lanza la excepción, entonces el objeto queda fuera del alcance. Sin embargo, usted puede estar interesado en el caso cuando se asigna un objeto:

f = new Fraction(3.14159264, 4); 

En este caso, f también es inservible, porque el constructor no terminó el trabajo, y el puntero no se asignan. El destructor no se llama, y ​​la memoria es desasignada, por lo tanto, no hay forma de utilizar el objeto.

Por lo tanto, construya su objeto normalmente, no use excepciones si tiene la intención de utilizar la clase. Use una función de miembro is_exact() para decidir si es exacto después de la construcción.

+0

El destructor no se llama si el objeto se crea en la pila tampoco. – villintehaspam

+0

@villintehaspam, cierto, me estaba centrando en la variante descubierta. –

+0

El hecho importante es que si el constructor de Fraction lanza, la asignación al puntero 'f' nunca se realiza, por lo que la dirección de memoria devuelta por' new' de hecho no es accesible, independientemente de lo que ocurra con la memoria asignada. – mloskot

1

Cuando un objeto se produce una excepción al final del constructor, es el objeto válido o se trata de uno de los 'depende de la técnica de construcción'?

Sí, depende hecho. Quiero decir, depende de lo que signifique que el objeto es válido. Válido puede tener varios significados.

Lo que se sabe es que un objeto cuya construcción se ha interrumpido es un objeto parcialmente construido. Ahora, si considera la construcción parcial como un estado inválido, entonces sí, dicho objeto no será válido.

La destrucción es sin embargo garantizada de acuerdo con este esquema se especifica en C++/15,2:

un objeto que es parcialmente construido o parcialmente destruidos habrá destructores ejecuta para todos los de sus subobjetos totalmente construido, es decir, para subobjetos para los cuales el constructor ha completado la ejecución y el destructor aún no ha comenzado la ejecución .

Esto significa, que sólo los subobjetos de objeto construido parcialmente estarán debidamente destruidos pero destructor de sí mismo objeto construido parcialmente se no ser llamados.

#include <iostream> 
using namespace std; 
struct A 
{ 
    ~A() { cout<<"~A()\n"; } 
}; 
struct B 
{ 
    A a; 
    B() { throw 1; } 
    ~B() { cout<<"~B()\n"; } // never called 
}; 
int main() 
{ 
    try 
    { 
     B a; 
    } 
    catch (...) 
    { 
     cout << "caught\n"; 
    } 
} 
0

Si en cualquier fase durante el constructor se produce una excepción (y no atrapado dentro del constructor), no existirá el objeto.Todas las variables miembro que ya se han construido con éxito serán deconstruidas en el orden inverso exacto de construcción. Si la excepción fue lanzada desde un constructor de variables miembro o de otra manera dentro de la lista de inicialización, la variable miembro que no pudo construir no tiene su destructor llamado, y ninguno tiene lo que viene después.

En cualquier caso, suponiendo que usa RAII en todas partes, todos los recursos se liberan correctamente y no habrá ningún objeto al que acceder. En el caso de ptr = new Foo();, la variable ptr conserva su valor anterior. Del mismo modo smartptr.reset(new Foo()); no llamará a la función de reinicio en absoluto.

Observe la falacia de usar el operador nuevo en las expresiones que construyen otros objetos: somefunc(Foo(), new Bar());. Si el constructor Foo falla, puede haber una pérdida de memoria (dependiendo del orden en que el compilador procesó los argumentos).

Cuestiones relacionadas