|home| |posts| |projects| |cv| |bookmarks| |github|

Defered Resource Cleanup in Cpp

Say you have this thing:

struct Foo {
    // some fields
};

void foo_init(Foo* f) {
    // init Foo's fields
}

void foo_deinit(Foo* f) {
    // deinit Foo's fields
}

And let's say you use it like this:

int main() {
    Foo f1;
    foo_init(&f1);

    if (some_error) {
        return 1;
    }

    Foo f2;
    foo_init(&f2);

    if (some_other_error) {
        return 1;
    }

    foo_deinit(&f1);
    foo_deinit(&f2);

    return 0;
}

The code above is wrong. If some_error or some_other_error happen, the Foos will not be deinit'ed.

You have to you make sure that in every return path the Foos will be deinit'ed:

int main() {
    Foo f1;
    foo_init(&f1);

    if (some_error) {
        foo_deinit(&f1);
        return 1;
    }

    Foo f2;
    foo_init(&f2);

    if (some_other_error) {
        foo_deinit(&f2);
        foo_deinit(&f1);
        return 1;
    }

    foo_deinit(&f1);
    foo_deinit(&f2);

    return 0;
}

But it's easy to forget to add the foo_deinit in every return path for every Foo. Or even worse, there can be a lot of Foo-like resources to deinit in every return path. Then the problem gets even messier.

So is there a better way?

Answer: std::unique_ptr

int main() {
    Foo f1;
    foo_init(&f1);
    std::unique_ptr<Foo, void(*)(Foo*)> f1_ptr{&f1, &foo_deinit};

    if (some_error) {
        return 1;
    }

    Foo f2;
    foo_init(&f2);
    std::unique_ptr<Foo, void(*)(Foo*)> f2_ptr{&f2, &foo_deinit};

    if (some_other_error) {
        return 1;
    }

    return 0;
}

This will guarantee that foo_deinit will be called whenever the scope in which the given Foo was defined is finished.

You declare your Foo, init it and then create a unique_ptr for it. Then you're done, no need to worry if it will be deinit'ed in every path.

There is an extra benefit. The order of deinitialization must be the reverse order of initialization (i.e. if f1 is init'ed before f2, then f2 must be deinit'ed before f1). In the first version, you have to keep track of this. In the second version, it's guaranteed that the order is correct.