Tuesday, 4 February 2014

Ad-hoc RAII

RAII is great, but it's not always the most convenient to use for one-off cases. Let's say we want to open a file:

bool f(const char* filename) {
    FILE* file = fopen(filename, "rb");
    if (!file) {
        LOG("File %d could not be opened", filename);
        return false;
    }

    // Read from file

    fclose(file);
    return true;
}

A seasoned programmer should see that and worry that the file won't be closed properly if an exception is thrown or some other kind of early return occurs while reading the file. A non-C++ programmer may reach for 'finally':

bool f(const char* filename) {
    FILE* file = fopen(filename, "rb");
    if (!file) {
        LOG("File %d could not be opened", filename);
        return false;
    }

    try {
        // Read from file
    }
    finally {
        fclose(file);
    }

    return true;
}

C++ doesn't have finally (not in strictly compliant compilers anyway), so this is not an option. This construct also introduces undesirable nesting (especially if the 'read from file' bit is also deeply nested) and increases the visual distance between the code for the setup and shutdown, making it more likely that the two get out of sync as the function grows.

Instead, C++ has destructors and, in true RAII fashion, you would encapsulate the file resource in a class which opens the file on construction, closes it on destruction, and instantiate this class on the stack, so that the file is automatically closed when the object goes out of scope, no matter how the function ends.

You're likely to have such a RAII file class kicking around your codebase, but there are plenty of one-off cases where pulling in a whole library for a single class is overkill, or where the work is very specific to the task at hand and doesn't warrant defining a whole new class for this purpose. You also don't really want to have to define a class elsewhere in your file or project, away from the context of whatever it is you're cleaning up.

In these cases you can perform a little bit of ad-hoc RAII by defining and instantiating a local class with a destructor only. Let's stick with the file example:

bool f(const char* filename) {
    FILE* file = fopen(filename, "rb");
    if (!file) {
        LOG("File %d could not be opened", filename);
        return false;
    }

    struct fclose_t {
        ~fclose_t() {
            fclose(file);
        }

        FILE* file;
    } fclose_obj = { file };

    // Read from file

    return true;
}

The definition and instantiation of the local class ensures that its destructor will be called however the function exits, and that's what closes the file. We don't need a dedicated file class with member functions, private state, invariants, etc. when we just want to close the file. We just use an aggregate which we initialise using curly bracket initialiser syntax. Note too that the code which closes the file remains adjacent to the code which opened it, no matter how large 'Read from file' gets. This aids maintenance.

The biggest problem with this kind of code, known as a scope guard, is that all this boilerplate is ugly and a pain to type each time you need to utilise it. We'll see what we can do about that in a future post.

Incidentally, it should be noted that, as per normal destructor rules, you shouldn't do anything in your scope guard destructor that may throw an exception, as this will terminate the program if the code is invoked during stack unwinding.

3 comments: