Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Having a destructor take different actions depending on whether an exception occurred

I have some code to update a database table that looks like

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

I'd like to wrap the transaction logic in an RAII class so I could just write

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

but how would I write the destructor for it?

like image 334
dan04 Avatar asked Apr 09 '10 07:04

dan04


4 Answers

Use following:

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

When tr.commit() completes it sets the state to "commit done" and destructor does nothing, otherwise it rollbacks.

Checking for exception is bad idea, consider:

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

In this case you probably expect rather rollback then commit but commit would be done.

Edit: but if you still want it badly, take a look on std::uncaught_exception() but read this first http://www.gotw.ca/gotw/047.htm

like image 182
Artyom Avatar answered Nov 05 '22 10:11

Artyom


You might use the following logic:

  1. Add a commit_done boolean value initialized to false to your Transaction class.
  2. In your constructor, "begin" the transaction.
  3. Add a method to "commit" the transaction and update commit_done accordingly.
  4. In your destructor, call "rollback" only if commit_done is still false
like image 34
ereOn Avatar answered Nov 05 '22 10:11

ereOn


By removing the exception handling, you are crippling your RAII.

The code should be

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

The differences with your original code are what motivated my answer:

  1. There is nothing needed in the "catch" : The destructor will assume an automatic rollback unless the commit() method was called with success (which could, for example, set some private boolean member of DBTransaction to true). The catch is where the code will continue, assuming the transaction failed.

  2. You should create a dedicated exception (I named it DBTransactionRollback) to throw the moment something fails in one of your commands. Thus, the catch will only catch transaction rollback-motivated exception, and not other exceptions (like STL, etc.)

  3. The use of the exception mechanism enables you to put your code in multiple functions, called from this try/catch block of code, without having to deal with boolean returns and other error code returns.

Hope this answers your question.

like image 26
paercebal Avatar answered Nov 05 '22 11:11

paercebal


Easiest way I can think of would be to set a private member variable in the class in the exception, and test it / perform the appropriate action in the destructor.

like image 43
tzaman Avatar answered Nov 05 '22 11:11

tzaman