24 October 2016

Wrapping Resource Handles in Smart Pointers

Wrapping resource handles in Smart pointers, C++

Some time ago I covered how to use custom deleters with smart pointers. The basic idea is to specify a dedicated method that will be called when a pointer is released. One logical application of custom deleters might be resource handles like files or the WinApi HANDLE type. Let’s see how can we implement such thing.

Introduction

Modern C++ offers robust methods to minimize resource and memory leaks. You can almost forget about raw pointers and just be using smart pointers. By default, smart pointers work on pointers, but we can reuse their capabilities and apply it to resource management.

BTW: you can even watch the latest talk from Herb Sutter about “Leak-Freedom in C++… By Default” from CppCon 2016.

But let’s discuss some basic things: for example FILE* needs to be opened and closed (via fopen() and fclose() methods). If you forgot about closing the stream, you would risk not only some memory leaks but also locking file or even corrupting it. A similar thing happens for the HANDLE in WinApi, you need to create it and then release by CloseHandle().
BTW: The system will/should release all allocated resources by a process when that process is terminated, still this is no excuse to clean stuff on your own! See here: MSDN: Terminating a Process.

FILE handle

FILE handle is used in CRT api to manage files. We need to wrap the fclose() function in the deleter.

In both of pointer types we can use the following functor object:

// stateless functor object for deleting FILE files
struct FILEDeleter 
{
    void operator()(FILE *pFile)
    {
        if (pFile)
            fclose(pFile);
    }
};

Such stateless functor will make unique_ptr just as small as only one pointer (because of optimization, the functor object doesn’t need additional memory…) - see Empty base optimization.

Unique Ptr

For unique_ptr the deleter is part of the type:

using FILE_unique_ptr = unique_ptr<FILE, FILEDeleter>;

For convenience, we can wrap the fopen() function. Moreover, If you work with VisualStudio/Windows SDK then it’s probably better to use secure version: fopen_s():

FILE_unique_ptr make_fopen(const char* fname, const char* mode)
{
    FILE *fileHandle= nullptr;
    auto err = fopen_s(&fileHandle , fname, mode); 
    if (err != 0)
    {
        // print info, handle error if needed...
        return nullptr;
    }

    return FILE_unique_ptr(fileHandle);
}

Shared Ptr

using FILE_shared_ptr = std::shared_ptr<FILE>;

Deleters for shared_pointers are specified during construction:

FILE_shared_ptr make_fopen_shared(const char* fname, const char* mode)
{
    FILE *fileHandle = nullptr;
    auto err = fopen_s(&fileHandle, fname, mode);
    if (err != 0)
    {
        // handle error if needed
        return nullptr;
    }

    return FILE_shared_ptr(fileHandle, FILEDeleter());
}

Note, that such construction might be not as efficient as using make_shared. We could be more advanced here and create a custom allocator that handles creation and deletion of the resource. That way we could invoke allocate_shared.

Example

FILE_unique_ptr pInputFilePtr = make_fopen("test.txt", "rb");
if (!pInputFilePtr)
    return false;

FILE_unique_ptr pOutputFilePtr = make_fopen("out.txt"), "wb");
if (!pOutputFilePtr)
    return false;

That’s all! No need to worry about fclose now. This might not be a problem when there’s only one file. When there are more files, then you need to pay attention to call fclose when some operation fails (like opening a new file). As in our example, if the second file cannot be created, then there’s no need to take care of the first (already opened) file. It will be closed automatically on error.

HANDLE from WinApi

Similarly, as for FILE we can define deleter for HANDLE:

struct HANDLEDeleter
{
    void operator()(HANDLE handle) const
    {
        if (handle != INVALID_HANDLE_VALUE)
            CloseHandle(handle);
    }
};

Unique Ptr

HANDLE is defined as void*, so the unique_ptr type will be specified as:

using HANDLE_unique_ptr = unique_ptr<void, HANDLEDeleter>;

We can wrap the creation procedure inside the function:


HANDLE_unique_ptr make_HANDLE_unique_ptr(HANDLE handle)
{
    if (handle == INVALID_HANDLE_VALUE || handle == nullptr)
    {
        // handle error...
        return nullptr;
    }

    return HANDLE_unique_ptr(handle);
}

The above method is a quite important step to remember! Since Windows functions usually return INVALID_HANDLE_VALUE which is not the same as nullptr we need to make sure we error on such condition. Otherwise, we would happily store INVALID_HANDLE_VALUE as pointer value inside the unique_ptr and the test for validity if (myUniquePtr) would always evaluate to true. See this code for even safer version of such handling

Example

HANDLE type might come from different sources, in the example below we obtain it as a handler to a file:

auto hInputFile = make_HANDLE_unique_ptr(CreateFile(strIn, GENERIC_READ, ...));
if (!hInputFile)
    return false;

auto hOutputFile = make_HANDLE_unique_ptr(CreateFile(m_strOut, GENERIC_WRITE, ...));
if (!hOutputFile)
    return false;

Summary

In this article, I’ve quickly covered how to implement smart pointers for resources like file handles or generic Windows kernel handles. You could easily extend this and have a range of smart pointers for other types like texture handles, shaders even library’s custom handles (like SDL_Surface): every time you have a special code that needs to be executed on deletion there’s potential to wrap it as a deleter for a smart pointer.

Do you use such custom deleters in your applications?

What kind of resources have you wrapped?

BTW: If you liked this article, please sign up for my free newsletter.

References

Interested in new blog posts and occasional updates? Please sign up for my free newsletter.

Copyright Bartlomiej Filipek, 2016, Blogger platform
Any opinions expressed herein are in no way representative of those of my employers.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.