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.

4 thoughts on “Python-style ‘with’ in C++

  1. Fun fact: You can name this MACRO “using” and there will be no conflict with the C++ keyword “using” because the keyword can never be followed by brackets (parentheses).

    Pros: Your syntax highlighter will definitely highlight it; Looks like real C++-ish.

    Cons: Will break if the C++ committee ever expands the meaning of “using” in a way that allows it to be followed by brackets (parentheses).

    Like

  2. It doesn’t help when you already have an RAII class defined, as with Lock here, but I am hoping that similar cases will be convertible to mozilla::ScopeExit (see https://bugzilla.mozilla.org/show_bug.cgi?id=1180299 ). The advantange with ScopeExit is that you can use a lambda to put the cleanup action inline, which has the side effect of making it much more obvious what the extra scope is for:

    {
    m.Lock();
    ScopeExit cleanup([&] {
    m.Unlock();
    });

    n++;
    }

    There’s also the convention of using “Auto” in the name of RAII classes. Your example would really be MutexAutoLock, right?

    Like

Leave a comment