Use std::unique_ptr to Safely Wrap Handles

, in Computing

This may be old news to experienced C++ programmers but I still see code out there that does not follow good RAII practice, particularly when interfacing with C APIs and their ratty handles.

I am talking about the FILE*, CURL*, sqlite3* handles that litter our code, not to mention the various HANDLEs and whatnots that exist and expect us to remember to clean up after them.

Here is an example, using FILE* (itself bad practice but this is just an example):

void BadPractice(const std::string& filename)
{
    FILE* f = ::fopen(filename.c_str(), "r");  // remember to fclose
    ::getc(f);    // do something with the file
    ::fclose(f);
}

Here we open a file, read a character, and then close the handle - simple.

Simple but dangerous.

The problem is that you need to ensure that ::fclose() is always called, lest f is leaked - in this example it is easy but code has a way of becoming more complex over time. For instance, adding an early return statement to this function is complicated by having to add ::fclose() before each return. Even if you sure you have protected all code paths, calling functions that throw exceptions will also leak.

As C++ programmers we know better. Data structures should clean up after themselves, and C-style handles that are not house-trained should be wrapped.

It is possible to manually wrap handles in custom objects but there is an easier way - use std::unique_ptrs!

As well as managing memory unique_ptrs can store anything that looks like a pointerSadly this does NOT include linux file descriptors as returned by socket, etc because 0 is a valid value and std::unique_ptr treats it differently. There are ways around even this problem but they are out of scope of this post. with a custom deleter that will be called whenever the instance goes out of scope.

There are at least two ways of doing this, I'll show both below.

void Method1(const std::string& filename)
{
    auto f = std::unique_ptr<FILE, int(*)(FILE*)>{
        ::fopen(filename.c_str(), "r"),
        &::fclose};

    ::getc(f.get());
} // f is automatically closed here

Here we construct the unique_ptr with the result of fopen() and the address of the function (fclose) that we want to call when the instance goes out of scope.

Although the result is good, this method has two disadvantages:

It is possible to improve on this.

struct FileDeleter
{
    using pointer=FILE*;  // not strictly required
    void operator()(FILE* f){
        ::fclose(f);
    }
};

using FileHandle =std::unique_ptr<FILE, FileDeleter>;

void Method2(const std::string& filename)
{
    FileHandle f{::fopen(filename.c_str(), "r")};

    ::getc(f.get());
}

This is more typing but better in nearly every way.