UPDATE: I’ve written a follow-up post on how to implement the idea presented in this post in a simpler and more flexible way. If you’re thinking about using this technique, I’d encourage you to also read the follow-up.
A large number of very useful libraries are written in C or only expose a C interface. There are many good reasons for that: C APIs are easy to bind to and use from pretty much any other (common) language, integration on a binary level (i.e. linking to a pre-built binary, not building the code as part of your project) is much easier than with C++ libraries, etc. Unfortunately, C doesn’t have any facilities for automatic memory management like C++ has with RAII and smart pointers. There are other C++-only features that you might miss when working with a C library, like better type safety, exceptions, operator overloading, object-oriented interfaces etc., but for some of these (e.g exceptions), opinions are very different on how desirable they actually are. Furthermore, with a well-designed C API, I find that the only thing I’m really missing is not having to worry about freeing resources. In this post, I’m going to show a technique which allows you to easily add RAII support to a C library that you’re using from C++, without having to do the work of writing custom wrapper classes for each object type provided by the library.
The C way: ‘Create’ and ‘Destroy’ functions
Many C libraries follow a common pattern for managing the lifetime of objects/resources: A pair of ‘create’ and ‘destroy’ functions for each type of object offered by the library.
To make this a bit more concrete, let’s have a look at an example: The SDL is a cross-platform library for writing games and multimedia applications. It offers input handling, 2D-rendering, window creation, and many other features in an OS-independent way. A wide range of platforms is supported, including the major three desktop operating systems, but also mobile platforms and the Raspberry Pi.
In order to draw a picture on the screen using SDL, we would have to create a window, create a renderer, load the picture into a texture, and then draw that texture using the renderer. These three components, window, renderer, and texture, are all represented by corresponding types in the library. In turn, there are creation and destruction functions for each of these types.
Here’s how using those functions looks like when manually managing destruction:
Now, let’s look at what we can do to have those destruction functions automatically called when the corresponding objects go out of scope, like we are used to in C++.
Using std::unique_ptr with a custom deleter
By default, std::unique_ptr uses
delete to destroy the object it points to when it goes out of scope. But this won’t work correctly with the pointers returned from a function in a C library. One issue is that we cannot use
delete to free memory that wasn’t allocated with
new, and the C library will probably either use
malloc(), or maybe even a custom memory allocator. But we also don’t know what other cleanups might be performed by the library’s destruction function. For example, an
SDL_Window might contain pointers or handles to other resources which need to be freed as well. Therefore, it’s important to make sure we use that destruction function. Luckily,
std::unique_ptr has support for a so-called “custom deleter”:
If we pass a function pointer for
std::unique_ptr‘s constructor, and also specify the type of the deleter as a second template argument, the smart pointer will call that function instead of using
delete. This gives us what we want: We can create a window, and then be sure that it’s going to be destroyed properly as soon as it goes out of scope. This removes the added noise and mental overhead of deallocation calls from our code, makes sure we correctly free even in the presence of exceptions, etc. But the unfortunate thing is that the syntax is quite verbose, and we would also have to repeat this whole incantation every time we want to use a pointer to an object of that type. So let’s try to make this more convenient.
Automatically determining the right deleter
Usually, the number of distinct objects we want to use from a library won’t be very large. This makes it feasible to write some utility code to map from the type of object to the corresponding deleter function. In C++ 17, we can use
constexpr if for this task, which makes it really straight-forward, but even in previous versions of the language, we can achieve the same thing fairly easily with a bit of template-metaprogramming. Let’s have a look a the
constexpr if version first.
We define a function
deleterFor which takes no parameters and one template parameter, the type of C-library object we’d like to get the deleter function for. We then compare the type of that template argument to all supported object types by using the type trait std::is_same, and return a pointer to the correct destruction function based on that. If the type isn’t known, we emit an error using a
With the help of that function, we can now define a custom
Ptr type, which is based on
std::unique_ptr, but only requires us to pass the object-type as a template argument, and figures out the right deleter by itself:
First, we define a helper template
PtrBase to save us some repetitive typing. Then we define a new class
Ptr, which inherits
std::unique_ptr by means of our helper template. We then define our constructor to forward the pointee to the base class’ (i.e.
std::unique_ptr) constructor, and additionally pass the deleter function as well. For the default constructor, we pass
nullptr to our first constructor. This is because a
std::unique_ptr with a custom deleter doesn’t allow being constructed with a
nullptr-deleter, which would be the case if we simply defaulted the constructor. By invoking the other constructor and passing a
nullptr-pointee, we are still assigning a valid deleter.
Now, with this helper class in place, we can rewrite our example from the beginning:
And there we go – no more manual deletion necessary. If we want to use more types from this library in the future, we simply have to add another case to the deleterFor function. Same thing if we want to integrate another C library which follows the same pattern.
Implementing deleterFor in C++ 14/11
If we can’t use
constexpr if, we can still write
deleterFor, and the usage is going to stay the same. Here’s how that would look like:
If you’re familiar with template meta-programming, this will be fairly easy to understand, but it probably seems rather daunting if not. It’s definitely more complicated compared to the
constexpr if version. The way it works is by using template specialization. We first define a base template, and then one specialization for each type of object we want to support. The specializations simply provide a static function
deleter() which returns the correct destruction function pointer. Then, in
deleterFor, we instantiate the
DeleterFor<> template using the type that was passed in via our template parameter, invoke the
deleter() function provided by the instantiated type, and return the result. Since we have a matching specialization for each supported type, this is going to call the
deleter() function of that specialization, and thus return the correct function pointer. If the type is unknown, the base template will be used by the compiler instead, which doesn’t have a
deleter() member. It does have its
exists member set to false though, which we use to provide an understandable error message. If we were to omit this whole
exists business, finding the right deleter would still work as long as we pass a known type to
deleterFor, but the compiler error would be more confusing in case the type is not known. The compiler would try to call
deleter() on an instantiation of the base template, which doesn’t have a
deleter member, and thus, the compiler would complain about that missing member instead of telling us that we passed an unknown type.
In this post, I presented a useful technique for making the types provided by C libraries easily usable with
std::unique_ptr, thus allowing us to make use of C++’s automatic memory management features even when working with C libraries. We didn’t have to write any wrapper classes, and if we want to add more types from a library we are already using, or integrate a different library that follows the same creation/destruction function approach, we simply need to extend the list of mappings. We looked at two approaches, a simple one based on C++ 17’s
constexpr if, and a more involved one based on template meta-programming, which also works in C++ 11 and 14.
Given the large number of mature, well-maintained, and useful C libraries, I think many C++ projects are still going to use a certain number of those libraries, which makes something like this very handy to have around.