2010-10-31 21 views
5

Supongamos que construyo un objeto RAII, y ese objeto puede fallar al construirlo. ¿Cómo manejo esto?Cuando un objeto RAII falla al construir

try { 
    std::vector<int> v(LOTS); 
    // try scope ends here because that's what the catch is for 
} catch(const std::bad_alloc&) { 
    // ... 
} 
// v? what v? 

Por supuesto, el constructor por defecto de std::vector no va a tirar y que puede ayudar, pero esto no es el caso general. Un constructor puede arrojar muy bien. Si deseo manejar cualquier falla en la adquisición de recursos, ¿cómo puedo hacer eso mientras sigo siendo capaz de proceder si no tiene tiro?

Editar: Para aclarar, mi problema es que si un recurso no puede adquirir, es posible que desee intentarlo de nuevo, y así sucesivamente. Tal vez pueda intentar adquirir un recurso alternativo.

+1

No exactamente seguro de lo que es la cuestión. El código que usa v debe estar en el bloque try. – Dialecticus

+2

Según entiendo la pregunta, el problema es que para poder recuperar una excepción en el constructor de 'v',' v' debe declararse dentro del alcance 'try', lo que significa que ya no se puede visible * después de * el bloque 'catch'. Entonces, si usted tiene un código que por un lado necesita poder "ignorar" una excepción cuando construye 'v', y por otro lado, ser capaz de usar' v' si la construcción tuvo éxito, se vuelve un poco complicado – jalf

+0

Si recurso no puede adquirir entonces tal vez puedo hacer con otro recurso en lugar de dejar que la excepción se propague. Y si ese segundo recurso falla, entonces tal vez tenga otra idea, y así sucesivamente. Siento que esto lleva a un desastre en el código sin importar el enfoque que tome. RAII simplemente describe cómo limpiar en caso de falla, no cómo resolver la falla. – wilhelmtell

Respuesta

7

Depende de lo que quieres decir con "proceed". Cualquier operación que requiera el recurso fallará: eso es lo que "requiere" significa. Así que cuando usted quiere continuar después de un error, que podría terminar de escribir código como este:

void something_using_RAII(thingummy &t) { 
    vector<int> v(t.size_required); 
    // do something using v 
} 

... 

for each thingummy { 
    try { 
     something_using_RAII(this_thingummy); 
    } catch(const std::bad_alloc &) { 
     std::cerr << "can't manage that one, sorry\n"; 
    } 
} 

Es por eso que sólo debe capturar las excepciones cuando hay algo que vale la pena que puede hacer con ellos (en este caso, la insuficiencia informe y pasar a la siguiente cosita).

Si desea intentarlo de nuevo en caso de fallo, pero sólo si falla el constructor, no se si todo lo demás falla:

while(not bored of trying) { 
    bool constructor_failed = true; 
    try { 
     vector<int> v(LOTS); 
     constructor_failed = false; 
     // use v 
    } catch(...) { 
     if (!constructor_failed) throw; 
    } 
} 

Esto es más o menos cómo std::new_handler obras - el controlador se denomina en el captar cláusula de un bucle similar, aunque sin necesidad de una bandera.

Si quieres probar un recurso diferente en caso de fallo:

try { 
    vector<int> v(LOTS); 
    // use v 
} catch(...) try { 
    otherthing<int> w(LOTS); 
    // use w 
} catch(...) { 
    // failed 
} 

Si "el uso v" y "uso w" son básicamente el mismo código, a continuación, refactorizarán en una función y lo llaman de ambos lugares. Tu función está haciendo bastante en este punto.

+2

'+ 1' solo para la última frase. No hay suficientes programadores que presten atención a eso. Muy desafortunadamente – sbi

7

Si se lanza un constructor RAII, todos los recursos vinculados a los objetos RAII antes del punto de lanzamiento se limpiarán correctamente. Las reglas de C++ están razonablemente diseñadas para garantizar eso.

Si su construcción v lanza a causa de una bad_alloc entonces cualquier objeto RAII creado antes de la v en el bloque try se limpia correctamente.

Por lo tanto, si en consecuencia usa RAII, no necesita un manual try/catch así porque los objetos RAII manejan la limpieza por usted. Si lo necesita lo necesita por alguna razón, en el caso anterior puede usar swap como el siguiente.

std::vector<int> v; 
try { 
    std::vector<int> vtry(LOTS); 
    v.swap(vtry); // no-throw 
} catch(const std::bad_alloc&) { 
    // ... 
} 
// v! 
+1

No se trata de limpiar. Se trata de hacer algo cuando el objeto RAII no se inicializa. Si no hago nada, la excepción se propaga. No es bueno. ¿Qué pasa si mi función debe proporcionar una garantía de no-tiro? ¿Qué pasa si el objeto RAII no tiene un constructor que no lanzará? – wilhelmtell

+0

Puede decirse que un objeto que no tiene un constructor que no arroja está mal diseñado. Esta bien. Nunca vi esta recomendación en ningún lado, así que si eso es cierto, quiero sacarlo a la superficie. De lo contrario, escucha cómo manejas la situación cuando cualquier ctor puede lanzar. – wilhelmtell

+0

@wilhelmtell, "¿Qué pasa si el objeto RAII no tiene un constructor que no lanzará?" -> Creo que necesitas poner el código completo dentro de la cláusula 'try' entonces. No creo que sea un mal diseño no tener un constructor sin tiro. A veces puede no encajar bien. Pero me parece útil si existe ese estado de no lanzamiento. También puede envolver su objeto en un puntero inteligente que tenga un estado predeterminado de nothrow (como 'shared_ptr'). Pero creo que eso sería desagradable. Los bloques Try tienen costo de ejecución cero en C++, si se realizan correctamente con tablas. –

2

Si no se puede crear v, no se puede ejecutar todo el código que intenta usar v. Mueva el catch después del código que usa el código v, en un lugar donde es razonable continuar la ejecución si no hay v.

+2

El problema, según tengo entendido, es que no se puede distinguir en la captura de una excepción lanzada por el cdor de v o por el otro código en el bloque de prueba. –

+0

@Roger: en cuyo caso (salvo la solución de litb, que está bien pero se basa en 'swap', que no todas las clases de RAII tienen), debe escribir algo molesto como' try {vector v (LOTS); bool flag = verdadero; intente con {use v; flag = falso; } catch (...) {X}} catch (...) {Y} '. X maneja excepciones del otro código en el bloque try, Y maneja excepciones del constructor * o destructor * de 'v', y de X. Pero el destructor de' v' no debe arrojarse, y se pueden reconocer excepciones de X. por la bandera. –

0

Todo código que use v debe estar en el bloque try.Si la pregunta es cómo entonces a reducir el código que lanzó la excepción, puede utilizar algún tipo de bandera para indicar en qué parte del bloque try usted es, de esta manera:

string flag; 
try 
{ 
    flag = "creating vector<int> v"; 
    std::vector<int> v(LOTS); 

    flag = "performing blaggity bloop"; 
    blaggity_bloop(); 

    flag = "doing some other stuff"; 
    some_other_stuff(); 
} 
catch(const std::bad_alloc&) 
{ 
    cerr << "Bad allocation while " << flag << endl; 
} 
Cuestiones relacionadas