Topic: N1968 and exception rollback utilities


Author: "=?iso-8859-1?q?Ion_Gazta=F1aga?=" <igaztanaga@gmail.com>
Date: Tue, 18 Jul 2006 07:00:12 CST
Raw View
Hi to all,

After reading N1968 ("Lambda expressions and closures for C+") I think
that this lambda syntax could be expanded to fill a big gap in C++:
easy exception handling with RAII.

In several mailing lists there have been proposals to implement
language constructs that would trigger exception handling code without
the need of try/catch constructs. An example of this is Andrei
Alexandrescu's on_scope_xxx() approach that was presented in
comp.std.c++ "A safer/better C++?" thread. And this approach was
implemented in D: http://www.digitalmars.com/d/exception-safe.html

With lambda expressions we can write RAII exception code without
writing an specific class to rollback each step if the next step
throws. For example, with a function and a class:

template<class Rollback>
rollback_object<Rollback> rollback(bool &do_rollback, Rollback &&r)
{  return rollback_object<Rollback>(do_rollback, std::move(r)); }

//A standard rollback object function that calls the user
//provided rollback in the destructor
template<class Rollback>
class rollback_object
{
   //The registered rollback should not throw
   template <class R>
   rollback_object(bool &do_rollback, R &&r)
      :  r_(std::move(r)), do_(do_rollback)
   {}

   rollback_object(rollback_object &&rback)
      :  r_(std::move(rback.r)), do_(rback.do_)
   {   rback.do_ = false;  }

   ~rollback_object()
   {
      //Call rollback if it's enabled
      if(do_)  r_();
   }

   Rollback r_;
   bool &   do_;
};

we could implement a function that has to insert a value in many
containers and check an external condition. If any of the insertions
throw or the external condition is not met, the operation should offer
strong guarantee (sorry for any code error!):

//This function does nothing if any operation
//throws or an external condition fails
bool class::insert_in_several_containers(const MyClass &myclass)
{
   { //Rollback code begin

   bool do_rollback = true;

   //This can throw
   vector_.insert(vector_.begin(), myclass);

   //Rollback function for exceptions or false return value
   auto rollback1 = rollback
      //If true, the rollback is executed
      (do_rollback
      //Rollback function to execute
      ,<>()->extern(vector_) { vector_.erase(vector_.begin()); });

   //This can throw
   list_.insert(list_.begin(), myclass);

   //Rollback function for exceptions or false return value
   auto rollback2 = rollback
      //If true, the rollback is executed
      (do_rollback
      //Rollback function to execute
      ,<>()->extern(list_) { list_.erase(list_.begin()); });

   //This can throw
   auto it = multiset_.insert(myclass);

   //Rollback function for exceptions or false return value
   auto rollback3 = rollback
      //If true, the rollback is executed
      (do_rollback
      //Rollback function to execute
      ,<>()->extern(multiset_, it) { multiset_.erase(it); });

   //Check if all is ok
   if(!all_ok()){
      //Rollback executed!
      return false;
   }

   //All ok, disable rollback
   do_rollback = false;

   } //Rollback code end

   return true;
}

We could use macros to offer the following interface:

ACME_ROLLBACK_BEGIN is equal to

   { //Rollback code begin

   bool do_rollback = true;

ACME_ROLLBACK_END is equal to

   } //Rollback code end

ACME_ROLLBACK_REGISTER(lambda_expr) is equal to

   //Rollback function for exceptions or false return value
   auto rollback##__LINE__ = rollback
      //If true, the rollback is executed
      (do_rollback
      //Rollback function to execute
      ,lambda_expr)

ACME_ROLLBACK_CANCEL is equal to

   //All ok, disable rollback
   do_rollback = false

So we could something similar to (not compiled code, just to have an
idea):

bool class::insert_in_several_containers(const MyClass &myclass)
{
   ACME_ROLLBACK_BEGIN

   //This can throw
   vector_.insert(vector_.begin(), myclass);

   ACME_ROLLBACK_REGISTER
      (<>()->extern(vector_) { vector_.erase(vector_.begin()); });

   //This can throw
   list_.insert(list_.begin(), myclass);

   ACME_ROLLBACK_REGISTER(<>()->extern(list_) {
list_.erase(list_.begin()); });

   //This can throw
   auto it = multiset_.insert(myclass);

   ACME_ROLLBACK_REGISTER(<>()->extern(multiset_, it) {
multiset_.erase(it); });

   //Check if all is ok
   if(!all_ok()){
      //Rollback executed!
      return false;
   }

   //All ok, disable rollback
   ACME_ROLLBACK_CANCEL();

   ACME_ROLLBACK_END

   return true;
}

The equivalent of D's "scope(exit)" is a normal RAII class;

ACME_ON_EXIT (lambda_expr) is equal to

   //Rollback function for exceptions or false return value
   bool always_true = true;
   auto rollback##__LINE__ = rollback
      //rollback is always executed
      (always_true
      //Rollback function to execute
      ,lambda_expr)

we can just create a new scoped class instead of reusing rollback.

The equivalents of scope(success) and scope(failure) are not easy to
imitate, but we have ACME_ROLLBACK_REGISTER that needs explicit success
notification but has the advantage of treating return values as errors
instead of only exceptions.

The only condition is that the lambda expression, the move constructor
of the closure and the closure's operator() shouldn't throw. Seen that
lambda expressions can be very useful to improve exception handling
code in C++, shouldn't we extend lambda proposal to include native
rollback functions for RAII exception handling? Or maybe we just need a
newer proposal that can handle any function object, but it's likely to
be used with lambda expressions? Let's make exception handling easier!

Regards,

Ion

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]