C++14 and SDL2: Managing Resources17 Apr 2014
Update: The final solution in this article has been updated based on feedback from nanofortnight on Hacker News. This change, while not solving the complexity of what it means to work with C++ templates, does help significantly with the readability of this solution.
Update 2: Added a final example that shows how the general make_resource helper can be used to simplify writing higher level C++ abstractions around C libraries
Update 3: In order to offer proper RAII semantics the make_resource function now throws if the resource fails to create. Thanks to Nicolas Guillemot for sticking out the discussions with me to help me see where this could be a potential hazard.
SDL2 is a cross-platform C library that provides developers with direct access to low level resources like the audio and graphics cards on an end user’s machine. Like most C libraries that provide access to resources, the SDL2 library’s API contains matching pairs of resource creation and deletion functions. These functions allocate and deallocate system resources making them perfect opportunities to leak said resources, if particular caution is not exercised. This article will show how C++14 can be used to manage resources created from a C based library such as SDL2.
In the case of SDL2 lets look at the example of creating an
SDL_Window resource, the application window that is drawn to the screen. While we will be focusing on the
SDL_Window in this article, this example is indicative of how all other SDL2 resources are created. Namely through the use of Creator and Destructor function pairs.
The problem is what occurs in the “do stuff here” region. If something occurs there that causes an exception, or otherwise prevents execution of the
SDL_DestroyWindow call, then resources are going to be leaked. In the case of the appication window this is generally not a concern as it means the application is closing and the operating system will clean everything up. However, in the case of frequently created resources such as sprite textures a leak can have significant impact on performance and stability of the application.
C++11 and 14 provide a tool that expresses single ownership of a resource,
unique_ptr. When the unique_ptr is deleted, such as when its parent container is destroyed or when it goes out of scope, a custom deleter can be called instead of the delete operator. This behavior is exactly the right mix for managing a resource created by a C library. This can be used to manage the window resource from the first code example.
Here a unique_ptr is created with two template parameters that represent the type of resource that is managed,
SDL_Window, and the signature of the deleter, a void function taking a pointer to an
SDL_CreateWindow is called and the result is immediately passed to the
unique_ptr as the first parameter and a function pointer to
SDL_DestroyWindow is passed as the second. At this point the
SDL_Window resource is now owned by the unique_ptr instance and when it goes out of scope, or is otherwise destroyed,
SDL_DestroyWindow will be called.
Great, now that the desired behavior has been achieved, but is there a way to make this process less verbose for users? Is there a way to generalize the creation of resources owned by a
unique_ptr? As with most things in C++ the answer is yes, with another layer of indirection.
Before getting into possible solutions, it is important to try imagining what the ideal scenario might look like. In this case the goal is to create a helper function that takes the creator and destructor from a C library that creates a resource, while at the same time still being able to pass in all the necessary initialization parameters.
Notice how this helper function no longer requires typing out the obscure
unique_ptr declaration upon initialization, auto can be used to bypass the verbose
unique_ptr declaration as its type can be easily deduced by the compiler in C++14. The first and second arguments are the C library functions that will do the work, followed by an arbitrary number of arguments that will be passed to the creator. The arguments take advantage of two more modern C++ features, parameter packs and variadic templates, a type-safe way of passing an arbitrary number of arguments.
With the declaration of the
make_resource function laid out, how might its implementation look? First, the type of the resource that the
Creator will create and the
Destructor will destroy needs to be deduced. Armed with that information the
unique_ptr can be created and initialized with the argument parameter pack.
There’s a lot going on in the body of this function. First, the resource is created on line 4, and line 5 verifies it was constructed properly, throwing if not. This ensures that any resource returned by
make_resource is guaranteed to be valid. The particular error thrown is a
std::system_error that captures the C
errno, turning it into an exception.
Finally, to construct the
unique_ptr, two bits of information are needed in the form of template parameters: the type of the owned resource and the type of the destructing function. Since the resource has already been constructed, decltype can be used on the resource
r to find its type. Since the actual type is going to be a pointer, this must be dereferenced first. In some cases, the deduced type could be a
std::decay is used to strip off any additional qualifiers. The second template parameter, which represents the function to be called on the owned resource when it is destroyed, is simple to deduce from the given
By having the resource be owned by a
unique_ptr, it allows for end users to have the most flexibility on controlling the lifetime. By default, the
unique_ptr implies single ownership of the resource. However, if the resource needs to be shared by a number of owners, the ‘unique_ptr’ return value can be used to initialize a
Now that the general solution
make_resource exists, one further step can be taken for individual libraries to make working with them even easier. First, create a namespace to store all of the helper functions in, in this case the
sdl2 namespace will be used. Next, for each resource creator in the library, add a helper function to wrap the call to
make_resource, thereby eliminating the need to pass the
Destructor explicitly and instead replacing it a with an appropriately named helper function. Below shows how this might look for the
The make_window helper can now be used in the following way, making for a safe and efficient way to create SDL2, or any other C library based, resources.
make_resource helper makes writing concrete helpers much simpler than it would be by hand. Then, those helpers can be used in higher-level wrapper objects, where the use of unique_ptr would allow the rule of zero to be invoked.