|
Boost : |
Subject: Re: [boost] D-style scope guards?
From: Lorenzo Caminiti (lorcaminiti_at_[hidden])
Date: 2011-03-05 19:24:38
On Fri, Sep 17, 2010 at 3:06 AM, Martin Christiansson
<martin.christiansson_at_[hidden]> wrote:
> Hi,
>
> I have used the good old scopeguard in the past and recently done some experiments with the Boost.ScopeExit library. Still I feel that the clean scope statements in D is missing.
>
> It will not be possible to do an equally clean solution without having the same features in the language itself, but I could reach as far as this example that reuses the ScopeExit library:
>
> void foo()
> { GUARDED_SCOPE_BEGIN
>
> GUARDED_SCOPE_EXIT() {
> cout << "Returning from foo()" << endl;
> } GUARDED_SCOPE_EXIT_END
>
> GUARDED_SCOPE_SUCCESS() {
> cout << "foo(): No Errors occurred!" << endl;
> } GUARDED_SCOPE_SUCCESS_END
>
> GUARDED_SCOPE_FAILURE() {
> cout << "foo(): Exception caught!" << endl;
> } GUARDED_SCOPE_FAILURE_END
>
> GUARDED_SCOPE_END
> }
I looked at what can be done with Boost.ScopeExit and/or Boost.Local
exits (they offer essentially the same functionality in this context).
1. SCOPE EXIT
First of all, note that Boost.ScopeExit code (and therefore,
Boost.Local exit code) is *not* executed when throwing an exception:
#include <boost/scope_exit.hpp>
#include <iostream>
#include <stdexcept>
void f() {
bool error = false;
BOOST_SCOPE_EXIT( (&error) ) {
std::cout << "returning" << std::endl; // Not executed on throw.
} BOOST_SCOPE_EXIT_END
throw std::runtime_error("some error");
}
int main() {
f();
return 0;
}
This will *not* print "returning" because the throw terminates `f()`
*without* executing local variables' destructors (which execute the
scope exit code).
2. ERROR NUMBERS
If instead of throwing, `f()` returns an error number (e.g., 0 if
success, etc) then `f()` can set a local variable `error` before
returning and the local exit code can check `error` to implement
D-style scope guards:
#include <boost/local/exit.hpp>
#include <iostream>
int f() {
int error = 0; // No error to start with.
BOOST_LOCAL_EXIT( (void) ) { // Same as D's `scope(exit)`.
std::cout << "exit" << std::endl;
} BOOST_LOCAL_EXIT_END
BOOST_LOCAL_EXIT( (const bind& error) ) { // Sane as D's `scope(success)`.
if (!error) std::cout << "success" << std::endl;
} BOOST_LOCAL_EXIT_END
BOOST_LOCAL_EXIT( (const bind& error) ) { // Same as D's `scope(failure)`.
if (error) std::cout << "failure" << std::endl;
} BOOST_LOCAL_EXIT_END
// Cannot use `return <NUMBER>` otherwise scope exits do not know the
// error number. Set error and exit using `return error` instead.
error = -2; return error;
error = -1; return error;
}
int main() {
std::cout << "error number: " << f() << std::endl;
return 0;
}
3. EXCEPTIONS
If `f()` throws, the situation is more complex. One approach could be
to program the 1st local exit to throw the exception while the
function code sets the `exception_error` object and returns instead of
throwing:
#include <boost/local/exit.hpp>
#include <iostream>
#include <stdexcept>
struct exception_error_interface {
virtual operator bool() const = 0;
virtual void throw_if() const = 0;
};
template<typename E>
struct exception_error: exception_error_interface {
template<typename A0>
explicit exception_error(A0 arg0): ex_(E(arg0)) {}
template<typename A0, typename A1>
exception_error(A0 arg0, A1 arg1): ex_(E(arg0, arg1)) {}
// Add more constructors for more parameters if needed...
operator bool() const { return true; }
void throw_if() const { throw ex_; }
private:
E ex_; // I need the exception type `E` here so I can throw it later.
};
template<>
struct exception_error<void> { // Use `void` for no error (cannot throw void).
exception_error(): err_() {} // No error by default.
template<typename E> // Construct with some error.
/* implicit */ exception_error(exception_error<E> const& err): err_(&err) {}
template<typename E> // Set some error.
exception_error& operator=(exception_error<E> const& err)
{ err_ = &err; return *this; }
operator bool() const { return err_ && bool(*err_); }
// Select proper type to throw via polymorphism.
void throw_if() const { if (err_) err_->throw_if(); }
private:
exception_error_interface const* err_;
};
void f() {
exception_error<void> error; // No error (i.e., `void`) to start with.
// This scope exit is special -- it's used to throw on exit if error.
BOOST_LOCAL_EXIT( (const bind& error) ) {
std::cout << "throwing if error" << std::endl;
error.throw_if(); // Throws if error set not to `exception_error<void>`.
} BOOST_LOCAL_EXIT_END
BOOST_LOCAL_EXIT( (void) ) { // Smae as D's `scope(exit)`.
std::cout << "exit" << std::endl;
} BOOST_LOCAL_EXIT_END
BOOST_LOCAL_EXIT( (const bind& error) ) { // Same as D's `scope(success)`.
if (!error) std::cout << "success" << std::endl;
} BOOST_LOCAL_EXIT_END
BOOST_LOCAL_EXIT( (const bind& error) ) { // Same as D's `scope(failure)`.
if (error) std::cout << "failure" << std::endl;
} BOOST_LOCAL_EXIT_END
// Cannot use `throw` otherwise scope exits are not executed. Set error
// and exit using `return` instead (the 1st scope exit will throw).
error = exception_error<std::runtime_error>("some error"); return;
error = exception_error<int>(-1); return;
}
int main() {
f();
return 0;
}
The difficulty is to program `exception_error` to actually throw the
correct exception type. This is done by storing the exception type `E`
and object within the `exception_error<E>` template and then using
polymorphism of the virtual function `throw_if()` to select the
correct `exception_error` object that should throw (which ultimately
throws the exception of the correct stored type `E`).
4. NOTES
This approach does not require any extra library support --
Boost.ScopeExit and/or Boost.Local exits are sufficient the way they
are. However, it does require programmers to use the local variable
`error` instead of returning the error number or throwing the
exception directly. However, I do not think the user code looks
cluttered from the code that manages `error`.
On Sun, Sep 19, 2010 at 7:43 AM, Ulrich Eckhardt <doomster_at_[hidden]> wrote:
> On Sunday 19 September 2010 10:40:16 Martin Christiansson wrote:
> [...]
>> boost::bind is used when calling the function with it arguments.
>
> Now this is a show-stopper for me, because bind creates a function object
> which implies copying the arguments, storing them somewhere, and that
> "somewhere" is possibly dynamically allocated, just to throw them away after
> the call. This is an insane overhead IMHO, though I'm doing embedded stuff and
> being mindful of resources is a must there, sometimes even at the expense of
> being able to debug something. YMMV.
The `exception_error` approach does not use `bind` but it does add
overhead (the virtual functions, etc). However, if programmers are
_really_ concerned about performances, they might decide to use error
codes instead of exceptions in the first place... or simply not to use
the above approach.
I could add a sketch of these examples into the Boost.Local docs. What
do you think?
Thanks.
-- Lorenzo
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk