When interacting with C APIs there is this common pattern:
foo *f;
init_foo(f);
int err = some_func(f);
if (err) {
free_foo(f);
return 1;
}
bar *b;
init_bar(b);
err = some_other_func(f, b);
if (err) {
free_bar(f);
free_foo(f);
return 2;
}
// etc
free_bar(f);
free_foo(f);
return 0;
This is quite verbose and error-prone. It's easy to forget adding a cleanup function to one of the error branches when doing a refactor.
One way to make it a bit nicer is using goto
.
foo *f = NULL;
bar *b = NULL;
int result = 0;
init_foo(f);
int err = some_func(f);
if (err) {
result = 1;
goto cleanup;
}
init_bar(b);
err = some_other_func(f, b);
if (err) {
result = 2;
goto cleanup;
}
// etc
cleanup:
if (b)
free_bar(b);
if (f)
free_foo(f);
return result;
But goto
s can be uncomfortable.
In C++ one could also use unique_ptr to automate the cleanup calls using a custom deleter.
std::unique_ptr<foo *, decltype([](foo *f) { free_foo(f); })> f;
init_foo(f.get());
int err = some_func(f.get());
if (err) {
return 1;
}
std::unique_ptr<bar *, decltype([](bar *b) { free_bar(b); })> b;
init_bar(b.get());
err = some_other_func(f.get(), b.get());
if (err) {
return 2;
}
// etc
return 0;
That works, but what I really want to have in these situations is defer
. Which is a way to defer execution until the end of the scope.
Repeated defer
s work as a stack, the last one executes first, then the previous and so on. Same as the destructors.
// example
#include <iostream>
int main(int argc, char** argv) {
defer({
puts("defer 1");
printf("argc = %d\n", argc);
for (int i = 0; i < argc; ++i) {
printf("\targv %d = %s\n", i, argv[i]);
}
});
defer({ puts("defer 2"); });
puts("hello");
}
Outputs:
hello
defer 2
defer 1
argc = 4
argv 0 = /app/output.s
argv 1 = foo
argv 2 = bar
argv 3 = baz
Building on unique_ptr
it's actually fairly easy to implement a defer
macro:
#ifndef defer
#include <memory>
#define CONCAT(x, y) x##y
#define DEFER_IMPL(expr, id) \
auto CONCAT(_defer_call, id) = [&](void*) expr; \
std::unique_ptr<void, decltype(CONCAT(_defer_call, id))> CONCAT( \
_defer_call_ptr_, id)((void*)1, CONCAT(_defer_call, id))
#define defer(expr) DEFER_IMPL(expr, __COUNTER__)
#endif
The macro just creates a unique_ptr
that has a deleter with a lambda that executes the given expression (expr
.)
It evaluates to something like:
auto defer_call = [&](void*) {
// expr
};
std::unique_ptr<void, decltype(defer_call)> defer_call_ptr ((void*)1, defer_call);
The actual value of the pointer does not matter to us, as it's never dereferenced. What's important is that it does not equal nullptr
, otherwise the deleter is not called by unique_ptr
, so I used the value 1.
One problem we have to overcome is multiple defer
s in the same scope. We need to use different variable names for them, otherwise the compiler will complain (rightfully so.)
This is where the __COUNTER__
macro comes in. __COUNTER__
evaluates to a number, starting at 0 and incrementing by 1 each time the macro is evaluated.
We can use this to create unique variable names for our defer implementation by concatenating a unique id with a prefix each time defer
is called.
So the above example expands to
auto _defer_call0 = [&](void *) { /*defer 1*/ };
std ::unique_ptr<void, decltype(_defer_call0)> _defer_call_ptr_0((void *)1,
_defer_call0);
auto _defer_call1 = [&](void *) { /*defer 2*/ };
std ::unique_ptr<void, decltype(_defer_call1)> _defer_call_ptr_1((void *)1,
_defer_call1);
puts("hello");