Bejegyzések (kivonat)
Bejegyzések (teljes)

C++11 BOOST_SCOPE_EXIT like thing

There are cases when you have to do some cleanup after you do something. It’s simple in C, or languages lacking exception support:

int success;
Foo* foo;

foo = new_foo();
if (!foo) return FAIL;

success = do_something(foo);
destroy_foo(foo);
return success;

However with exceptions things get complicated. If do_something can throw, then you’re screwed. Languages, like Java, Ruby, etc have a try..finally construct:

Foo foo = new Foo();
try {
    foo.do_something();
} finally {
    foo.destroy();
}

But the same in c++ is more complicated, because there is no finally. (Let’s assume new_foo and destroy_foo are some functions in some C library and you do_something() is some c++ code that may throw.)

Foo* foo = new_foo();
if (!foo) throw SomeException();

try
{
    do_something(foo);
}
catch (...)
{
    destroy_foo(foo);
    throw; // rethrow exception
}
// cleanup also when nothing went wrong:
destroy_foo(foo);

There must be a better way. It’s not too complicated now, but add a more complicated cleanup, more foo like objects, and the disaster is ready. But also in the C case (you need an if and deinitialization after every function call that can fail, or goto to a common cleanup section…), and in java (nested try blocks, that you can abstract away with a bunch of functions…).

But C++ has destructors, which Java and other GC’d languages lacks.1 And also the RAII idiom. It’s great thing, which simplifies the code a lot, and you can write something like this:

FooWrapper foo;
do_something(foo.get_foo());

And foo will be destroyed, whenever foo leaves scope. So, unlike the finally solution, it’s also great if you have complicated control flow where the code can return at arbitrary positions. You don’t have to worry about placing destroy_foo(foo); before each return. However, the wrapper class must be written:

class FooWrapper : boost::noncopyable
{
public:
    FooWrapper() : foo(new_foo())
    {
        if (!foo)
            throw SomeException();
    }
    ~FooWrapper() { destroy_foo(); }

    Foo* get_foo() { return foo; }

private:
    Foo* foo;
};

But actually is should also include a wrapper for every method the C library includes, and provide some copy mechanism (some kind of reference counting probably). Congratulations, you have successfully written a C++ wrapper around the library, you should publish it somewhere

The point is, there are cases when you’re stuck with some C API that you must use, but writing a full wrapper is overkill, as you only use a few functions in only one place. Or you need some other finally like thing, like you must update a record in a database after do_something finished, regardless it succeeded, or not. And a class like WriteSomethingToDatabaseOnDestruct is a little bit funny

I’m not the only one who had this problem. Actually, there is BOOST_SCOPE_EXIT that solves this. Using it, you can write something like:

Foo* foo = new_foo();
if (!foo) throw SomeException();

BOOST_SCOPE_EXIT(foo)
{
    destroy_foo(foo);
} BOOST_SCOPE_EXIT_END

do_something(foo);

The BOOST_SCOPE_EXIT thing is a bit verbose, uses a lot of preprocessor magic, but it works. For simple cases I usually define the following:2

#define AT_SCOPE_EXIT(x) AT_SCOPE_EXIT_SEQ(, x)

#define AT_SCOPE_EXIT_SEQ(seq, x)               \
  BOOST_SCOPE_EXIT(seq)                         \
  {                                             \
      x;                                        \
  } BOOST_SCOPE_EXIT_END                        \
  do { } while(0)

Then you can use it like:

Foo* foo = new_foo();
if (!foo) throw SomeException();
AT_SCOPE_EXIT_SEQ((foo), destroy_foo(foo));

do_something(foo);

Boost scope exit works by declaring a class using stuff between BOOST_SCOPE_EXIT and BOOST_SCOPE_EXIT_END as a destructor, and capturing the variables. If you try to capture this, it will bite you. If you put multiple BOOST_SCOPE_EXIT into one line (for example as a result of macro expansion) it will bite you. But in the latter case you probably abusing it

But hey, in C++11 we have lambdas! It can capture variables (also this, because lambda body is not a method in some random class), you declare them inline, not somewhere outside the function, where you can’t find it quick

So here is a POC:

#include <functional>
#include <boost/noncopyable.hpp>

class AtScopeExit : boost::noncopyable
{
public:
    AtScopeExit(std::function<void ()> func) : func(func) {}
    ~AtScopeExit() { func(); }

private:
    std::function<void ()> func;
};

And use it like this:

Foo* foo = new_foo();
if (!foo) throw SomeException();
AtScopeExit x([foo]() { destroy_foo(foo); });

do_something(foo);

Much cleaner IMHO. You need to give it a name (since there are no anonymous variables in c++). You can hide it in a macro, but then you’ll face similar problems if you somehow place multiple declarations in one line (unless you use the nonstandard __COUNTER__ macro instead of __LINE__).

The catch? Take this example:3

#include <boost/scope_exit.hpp>

int main()
{
    const char* c = "foo";
    BOOST_SCOPE_EXIT(&c)
    {
        printf("%s, world!\n", c);
    } BOOST_SCOPE_EXIT_END
    c = "Hello";
    printf("Testing...\n");
    return 0;
}

If you compile it with clang++ -std=c++11 -O3, you get the following (I’ve manually removed uninteresting parts):

@.str1 = private unnamed_addr constant [6 x i8] c"Hello\00", align 1
@.str3 = private unnamed_addr constant [12 x i8] c"%s, world!\0A\00", align 1
@str = private unnamed_addr constant [11 x i8] c"Testing...\00"

define i32 @main() nounwind uwtable {
  %puts = tail call i32 @puts(i8* getelementptr inbounds ([11 x i8]* @str, i64 0, i64 0))
  %1 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([12 x i8]* @.str3, i64 0, i64 0), i8* getelementptr inbounds ([6 x i8]* @.str1, i64 0, i64 0)) nounwind
  ret i32 0
}

It inlined and optimized away the whole scope exit thing, like you have written the following:

int main()
{
    puts("Testing...");
    printf("%s, world!\n", "Hello");
    return 0;
}

std::function is special class that can dynamically hold anything that can be copied and has an operator(). This requires some kind of run time polymorphism, and neither gcc nor clang is able to optimize it away. So I wrote the following:

template <typename T>
class AtScopeExit
{
public:
    AtScopeExit(const T& func) : func(func) {}
    AtScopeExit(const AtScopeExit&) = delete;
    AtScopeExit(AtScopeExit&& o) : func(o.func) { o.call = false; }
    ~AtScopeExit() { if (call) func(); }
private:
    const T& func;
    bool call = true;
};

template <typename T>
AtScopeExit<T> at_scope_exit(const T& func)
{
    return AtScopeExit<T>(func);
}

This doesn’t use std::function, or any fancy stuff, just some static templates that are evaluable at compile time (and some more c++11 features, like move constructor). Unfortunately, a helper function is required, because lamdas have undefined unique types, and you can’t deduce class types from their constructor. This also means some kind of copy/move semantics is required. This class has a move constructor which sets a flag on the old object to not run the lamda on destruction. But this is only ever used if your compiler doesn’t support return value optimization (RVO). Gcc and clang have it enabled even at -O0, so if you do not need to target other compilers you can remove the copy/move constructors and the call member, and it will continue to work correctly (but a valid copy/move constructor is still required, it’s just not called). But even in that case please leave it there, or it may bite you or whoever has to maintain your code sometime in the future

Back to the example, if you write

#include <stdio.h>

int main()
{
    const char* c = "foo";
    auto x = at_scope_exit([&c]() { printf("%s, world!\n", c); });
    c = "Hello";
    printf("Testing...\n");
    return 0;
}

Clang compiles it to4

@.str1 = private unnamed_addr constant [6 x i8] c"Hello\00", align 1
@.str3 = private unnamed_addr constant [12 x i8] c"%s, world!\0A\00", align 1
@str = private unnamed_addr constant [11 x i8] c"Testing...\00"

define i32 @main() nounwind uwtable {
"_ZN11AtScopeExitIZ4mainE3$_0ED2Ev.exit":
  %puts = tail call i32 @puts(i8* getelementptr inbounds ([11 x i8]* @str, i64 0, i64 0))
  %0 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([12 x i8]* @.str3, i64 0, i64 0), i8* getelementptr inbounds ([6 x i8]* @.str1, i64 0, i64 0)) nounwind
  ret i32 0
}

That’s it. 17 lines of simple C++ code vs. thousands of preprocessor magic. The only issue I’m aware of is that you shouldn’t throw in the lambda since it’s being called in a destructor (the boost version has the same limitation btw). Use it however you like.


  1. There are finalizers, but they are often considered obscure implementation detail rather than a feature, they have funky semantics5 (like they run in a separate thread, or what happens if you create a reference to an object being finalized…). There is also no guarantee when will the finalizer run, and in most cases, if it will be ever called (as GCs on shutdown usually don’t bother freeing up resources as the OS will do it). This is fine if all you need is to free up some memory allocated by a random c library, but otherwise it’s not enough.

  2. I wrote that code when scope exit didn’t support the comma separated list format using variadic macro, only boost preprocessor sequences, that’s why I use the deprecated syntax

  3. I’m using the C io functions here, instead of c++ iostream, because it doesn’t make the generated llvm ir unreadable.

  4. It actually created less garbage, as BOOST_SCOPE_EXIT caused clang to output initialization code for C++ iostream (in a global constructor). But since in real code you probably already use them, it’s not an issue.

  5. The only language I’ve seen where finalizers have pretty clear semantics is lua. When gc decides it’s time to destroy a object it checks if it has a __gc metamethod. If it has, it skips garbage collecting that object and collects these objects in a list. At the end of collection cycle, these finalizers executed in reverse creation order, and the __gc metamethod is cleared. So in the next cycle they appear as plain objects without a finalizer. And unlike most other languages, they are guaranteed to be called.

Szólj hozzá! (vagy ne…)