Python-style ‘with’ in C++

One of the most important idioms in C++ is “Resource Acquisition Is Initialization” (RAII), where the constructor of a class acquires a resource (like locking a mutex, or opening a file) and the corresponding destructor releases the resource.

Such classes are almost always used as local variables. The lifetime of the variable lasts from its point of declaration to the end of its innermost containing block.

A classic example is something like:

extern int n;
extern Mutex m;

void f() {
    Lock l(&m); // This is an RAII class.
    n++;
}

The problem is, code written with these classes can easily be misunderstood.

1) Sometimes the variable is used solely for the side-effects of its constructor and destructor. To a naive coder, this can look like an unused variable, and they might be tempted to remove it.

2) In order to control where the object is destroyed, the coder sometimes needs to add another pair of braces (curly brackets) to create a new block scope. Something like:

void f() {
    [...]

    {
        Lock l(&m);
        n++;
    }

    [...]
}

The problem is, it’s not always obvious why that extra block scope is there. (For trivial code, like the above, it’s obvious. But in real code, ‘l’ might not be the only variable defined in the block.) To a naive coder, it might even look like an unnecessary scope, and they might be tempted to remove it.

The usual solutions to this situation are: (a) Write a comment, or (b) trust people to understand what you meant. Those are both bad options.

That’s why I’m fond of the following MACRO.

#define with(decl) \
for (bool __f = true; __f; ) \
for (decl; __f; __f = false)

This allows you to write:

void f() {
    [...]

    with (Lock l(&m)) {
        n++;
    }

    [...]
}

This creates a clear association between the RAII object and the statements which depend on its existence. The code is less likely to be misunderstood.

If you’re familiar with Python, you’ll recognize this as analogous to Python’s with-statement.

Any kind of variable declaration can go in the head of the with-statement (as long as it can appear in the head of a for-statement). The body of the with-statement executes once. The variable declared in the head of the statement is destroyed after the body executes.

I didn’t invent this kind of MACRO. (I think I first read about something similar in a Dr Dobb’s article.) I just find it really useful, and I hope you do too.

Advertisements