
On Fri, Sep 17, 2010 at 3:06 AM, Martin Christiansson <martin.christiansson@gmail.com> 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@knuut.de> 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