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 ]