2010-04-09 9 views
6

Tengo algo de código para actualizar una tabla de base de datos que se parece aTener un destructor tomar acciones diferentes dependiendo de si se ha producido una excepción

try 
{ 
    db.execute("BEGIN"); 
    // Lots of DELETE and INSERT  
    db.execute("COMMIT"); 
} 
catch (DBException&) 
{ 
    db.execute("ROLLBACK"); 
} 

Me gustaría envolver la lógica de la transacción en una clase de RAII por lo que pude acaba de escribir

{ 
    DBTransaction trans(db); 
    // Lots of DELETE and INSERT 
} 

pero ¿cómo iba a escribir el destructor para que?

Respuesta

12

Uso siguiente:

transaction tr(db); 
... 
tr.commit(); 

Cuando tr.commit() completa que establece el estado a "comprometerse hecho" y destructor no hace nada, de lo contrario, reversiones.

Comprobación de excepción es mala idea, considere:

transaction tr(db); 
... 
if(something_wrong) 
    return; // Not throw 
... 
tr.commit(); 

En este caso es probable que en lugar de esperar reversión a continuación, confirmar, pero se comprometen se haría.

Editar: pero si todavía quiere mal, echar un vistazo en std::uncaught_exception() pero leer esta primera http://www.gotw.ca/gotw/047.htm

+1

+1 Así es como se hacen tales cosas. Sin embargo, hay un problema: ¿y si te olvidas de llamar a commit()? – sharptooth

+2

¿Y qué pasa si olvidas crear la variable de transacción? No puedes evitar todos los errores. –

+2

@sharptooth: ¿y si se olvidó de hacer los cambios que quería comprometer en primer lugar? No creo que haya mucho que puedas hacer para protegerte contra la incompetencia. – jalf

1

La manera más fácil que se me ocurre sería establecer una variable miembro privada en la clase de la excepción, y probarlo/realizar la acción apropiada en el destructor.

3

puede utilizar la siguiente lógica:

  1. Añadir un commit_done valor booleano inicializado a falsa a su clase de transacción.
  2. en su constructor, "comenzar" la transacción.
  3. añadir un método para "comprometer" la transacción y actualizar commit_done en consecuencia.
  4. En su destructor, lo llaman "reducción" sólo si commit_done sigue siendo falsa
2

Quitando el manejo de excepciones, que están paralizando su RAII.

El código debe ser

try 
{ 
    DBTransaction trans(db) ; 

    // Lots of DELETE and INSERT 
    // should one fail, a DBTransactionRollback exception will be thrown 

    trans.commit() ; 
} 
catch(const DBTransactionRollback & e) 
{ 
    // If really needed, you could extract failure information from "e" 
} 

Las diferencias con el código original se lo motivó mi respuesta:

  1. No hay nada necesario en la "captura": El destructor asumirá un automática rollback a menos que el método commit() fue llamado con éxito ( que podría, por ejemplo, establecer algún miembro de private boolean true DBTransaction). La captura es donde el código continuará, asumiendo que la transacción falló.

  2. Debería crear una excepción dedicada (la llamé DBTransactionRollback) para lanzar el momento en que algo falla en uno de sus comandos. Por lo tanto, la captura solo detectará la excepción motivada por retrotracción de transacción, y no otras excepciones (como STL, etc.)

  3. El uso del mecanismo de excepción le permite poner su código en múltiples funciones, llamadas desde este try/atrapar bloque de código, sin tener que lidiar con retornos booleanos y otros retornos de código de error.

Espero que responde a tu pregunta.

Cuestiones relacionadas