top | item 3693177

C++11: Try/Catch/Finally Pattern Using RAII & Lambdas

32 points| code-dog | 14 years ago |nerds-central.blogspot.com | reply

12 comments

order
[+] ot|14 years ago|reply
What the OP describes here is a common C++ pattern known as "scope guard" (check "Solution 4" in http://drdobbs.com/184403758). It was possible to implement it in C++03 with some tricks but C++11 lambdas make it much easier.

This is a very nice small implementation but, as always with C++, devil is in the details:

* Creation of an std::function needs a dynamic allocation, so if the allocation fails an exception will be thrown and if the finally is guarding a resource, the resource will be leaked

* std::function has a non-negligible calling overhead, hence this should not be used in performance-sensitive code

* Checking a condition inside the finally clause is not very elegant, a better idiom in C++ is to support dismissing a scope guard.

* The finallyClause may throw an exception, and since it is called in a destructor this is generally considered a bad idea. I don't know what could happen in this case, but some scope guard implementations I've seen catch the exception and explicitly call std::terminate(). I guess this is for performance reasons, because the destructor can be declared nothrow.

Here there is a more complex implementation, which addresses most of the corner cases:

http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stepha...

[+] code-dog|14 years ago|reply
I get your points. In general, the use cases for this pattern are not production code. The post points out this is not a good way of handling resource and that the example is about debugging. It would be up to the implementer to ensure the finally clause does not throw and exception or to catch it (I think there is something about nesting).

As for memory allocation failures, my experience is that depends a lot on the platform. It is a common misconception that dynamic memory is likely to run out and therefore we need to worry about that but not automatic memory. However, I have seen stack overflow many many many more times than a genuine 'out of memory'. Further, if an application has actually run out of memory so badly that a allocation of the closure's internal storage barfs then I suspect it cannot be retrieved. Naturally this does not apply if your platform has a very low heap size (e.g. realtime hardware or some such).

[+] evincarofautumn|14 years ago|reply
I’ve done this before, and this version has a significant flaw: it is not exception-safe. The destructor should not read as it does:

    ~finally() {
        finallyClause();
    }
This allows exceptions thrown by finallyClause() to propagate out of the destructor, which, if an exception is already being thrown, will result in a call to std::terminate(), causing the application to die horrifically. Because C++ kinda-should’ve-but-really-doesn’t have checked exceptions, we must simply discard them:

    ~finally() {
        try { finallyClause(); } catch (...) {}
    }
You can also avoid problems with exceptions and std::function’s dynamic allocation by using a template instead:

    template<class F>
    class finally_type {
    public:

        explicit finally_type(F f) : function(f) {}
        ~finally_type() { try { function(); } catch (...) {} }

    private:

        finally_type(const finally_type&);
        finally_type& operator=(const finally_type&);
        F function;

    };

    template<class F>
    finally_type<F> finally(F f) { return finally_type<F>(f); }
This can be used like so:

    void test() {
        int* i = new int;
        auto cleanup = finally([i]() { delete i; });
        may_throw();
    }

Finally (har!) the equivalent idiom in C++03:

    void test() {

        int* i = new int;

        struct finally {
            finally(int* i) : i(i) {}
            ~finally() { delete i; }
            int* i;
        } cleanup(i);

        may_throw();

    }
[+] ot|14 years ago|reply
> Because C++ kinda-should’ve-but-really-doesn’t have checked exceptions, we must simply discard them:

This would make the error pass silently, possibly leaving the program in an inconsistent state. It is probably better to call std::terminate() instead, which is what C++ does when an exception is thrown when another exception is active.

[+] code-dog|14 years ago|reply
That is really nice. Could you expand on how this works a little "You can also avoid problems with exceptions and std::function’s dynamic allocation by using a template instead:"
[+] NerdsCentral|14 years ago|reply
I was not expecting this to produce so much interest. I guess a lot of people have asked about how to implement try/catch/finally in C++.