Boost logo

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