Go-style 'defer' in C++

I was recently reading through an article on the code of the 2010 XBLA hit Braid and saw this amongst the code snippets:

...
auto f = fopen(result_name, "wb");
assert(f);
defer { fclose(f); };
...

Throughout the snippets, the author (Jonathan Blow) uses 'defer' to contain clean-up operations. This is not a built-in language construct, and so my curiosity was piqued.

In the language 'Go', defer allows the programmer to set an expression to execute at the end of a function. In Jonathan Blow's samples, he is able to create similar functionality and express that he wants particular things to happen with certainty once he loses scope - cleaning up memory and closing file streams being the displayed examples. And so, I ended up on a short quest to find out how this may have been implemented. Here's what I came up with:

struct Defer {
    template<typename Fn>
    struct Contain {
    private:
        Fn func;

    public:
        Contain(Fn func_in)
            : func(func_in)
        {}

        ~Contain() {
            func();
        }
    };

    template<typename Fn>
    Contain<Fn> operator=(Fn func) {
        return Contain<Fn>(func);
    }
};

#define CONCAT_IMPL(x, y) x##y
#define CONCAT(x, y) CONCAT_IMPL(x, y)
#define UNIQUENAME(s) CONCAT(s, __COUNTER__)
#define defer auto UNIQUENAME(_defer) = Defer() = [&]

int main() {
    printf("1\n");
    defer { printf("2\n"); };
    defer { printf("3\n"); };
    printf("4\n");

    return 0;
}

With the following output:

1
4
3
2

The defer macro works by creating an instance of the Defer class, which is assigned a lambda function on creation. The provided lambda is stored as a member of the inner struct Contain, and is called in the destructor of Contain. This makes it so that when the created struct instance goes out of scope, the deferred lambda function is called.

Each instance of Defer is given a unique name through the __COUNTER__ macro, which increments each time it is called. This allows us to have multiple defer expressions in the same scope without conflicting names. The defer macro expands to this:

auto _defer1 = Defer() = [&] {
    printf("I'll happen later!\n");
};

Two important notes

Mostly importantly: __COUNTER__ is not defined in the C++ standard. It has been supported by MSVC, GCC, and Clang for some time. To conform entirely to the standard, __LINE__ can be used instead as long as the caveats of that are made known to the programmers involved.

Less importantly, this implementation uses C++11 features (templates and lambdas). I explore a C++03 implementation in this code snippets piece.

Differences from 'defer' in Go

In Go, defer makes it so that an expression is executed at the end of function scope.

defer fmt.Println("I'll happen later!")

To defer something more, we can define that expression to be a closure:

x := 0

defer func() {
    x += 1
    fmt.Println(x)
}()

The biggest things to note about Go's defer implementation in comparison to our C++ hack are:

  • expressions are executed at the end of function scope
  • the state of variables are saved at the point defer is called

In this Go snippet, we defer the printing of a variable within a for loop:

func main() {
    for i := 0; i < 5; i += 1 {
        defer fmt.Println(i)
    }

    fmt.Println("end of main")
}

With this output:

end of main
4
3
2
1
0

We see that the state of i is saved at each defer call in the for loop, and that defer happens at the very end of the function.

In our C++ implementation we are creating instances of objects, which means our defer calls (which happen on object destruction) are subject to the lifetime of the objects, which is local scope. If we defer within a for loop or if statement, the destruction of our Defer object will happen when we leave that scope.

for(int i = 0; i < 5; ++i)
    defer { printf("%d\n", i };

Output:

0
1
2
3
4

Should I use it?

The first thing I thought upon seeing defer in the Braid codebase was "Nifty!", which is what prompted me to investigate further. But as with all 'nifty' things, I get torn up wondering whether it's worth introducing such concepts to a codebase.

On the one hand, this makes cleaning things up so much nicer. Working professionally in C, I see this kind of thing all the time:

void myfunc(void)
{
    char *str1 = strdup("...")
    char *str2 = strdup("...")

    if(false)
        goto out;

    /* Some other stuff */

out:
    free(str1);
    free(str2);
}

In the C++ community, especially with modern C++, the goto label is largely frowned upon. Common arguments are that jumps to different labels cause a block of code to become increasingly difficult to read and debug, and that it can prevent the compiler from making branch prediction optimisations. Its usage then has to come with style guidelines such as "Only use goto to go down, never up", and such guidelines have to be known and enforced by all contributors. It becomes simpler to just say "no", and thus goto is seldom seen in C++ codebases.

defer doesn't suffer from the same issues - the conditions and costs of a defer call are very clearly defined. We know when the code will execute, we know the order in which it will execute, and we can see the up front costs (the creation and destruction of the Defer object).

On the other hand ... C++ already has its own built in mechanism for handling what should happen to local objects when we leave scope. It's called RAII. And furthermore, it's what we're actually using to make our defer implementation happen! And so the argument comes, "If you're going to defer an fclose(), why haven't you made that into a File object? If you want to defer a free(str), why aren't you using an std::string? If you want to destroy a heap allocated object, why aren't you using std::unique_ptr?". These questions, like that of a goto, lead to long-waged wars of discussion that will last as long as the lifetime of the language.

My programming style in my personal work doesn't come from a textbook defintion of how to be a perfect modern C++ programmer. Not even close. As one of those increasingly controversial C-style C++ programmers, I see defer and think that it's actually a pretty neat concept I could see myself using. It does, however, go against concepts expected for a C++ programmer to be familiar with - introducing defer into a team using the C++17 spec as their bible is going to bring confusion and friction.

I would be disinclined to introduce it to a codebase where the team of programmers, and their possible expectations of a codebase, is not known in advance. I might still consider its proposal in some cases, and may make use of in personal projects in future. At the very least, it was fun to play around with Go for a few minutes and examine a construct that will surely be introduced into many more future languages.