Last Update:
Wrapping Resource Handles in Smart Pointers
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
- Bartek’s blog: Custom Deleters for C++ Smart Pointers - more about custom deleters.
- boost 2: shared_ptr wraps resource handles - an old article about using boost smart pointers for resources
- StackOverflow: Using std::unique_ptr for Windows HANDLEs