In the last few days, I needed to create a thin wrapper class that apply RAII patterns to handles coming from a C library. I did this several times in the past, but this time I think I come up with some new solutions, at least for me.

In this post, I’ll consider this toy C API (declarations have been reduced to have a nice layout):

enum result_type { OK, ERROR_MEMORY, ERROR_THIS }; result_type init(int flags); result_type get_version(int *version); result_type create_handle_a(handleA *returnedHandle, int param); result_type function_on_a(int param1, float param2, handleA a); // no destroy for handle_a result_type create_handle_b(handleB *returnedHandle, float param); result_type function_on_b(int *retval, int param, handleB b); result_type destroy_handle_b(handleB b);

This library uses a quite common convention, where all the functions return the very same type ( result_type ) so, as is common practice, the very first thing I wrote is a function that checks the result type, and converts that into exceptions (or does nothing, in case of success):

inline void check(result_type result) { switch (result) { case OK: return; case ERROR_MEMORY: throw std::bad_alloc(); case ERROR_THIS: throw std::logic_error("this happens"); default: throw std::runtime_error("unexpected error"); } }

Of course, I was ready to wrap all my calls in a “check” call, like check(function_on_b(handle, &result, param1)); , but then I realized that this C API is more regular than this:

There’re a bunch of free function that do not operate on any handle (the init function that must be called once, and other utilities such as get_version above)

function that must be called once, and other utilities such as above) There’re some special free function that construct new handles (e.g. create_handle_a )

) All the function that are supposed to operate on a specific handle are taking the handle as last parameter (e.g. function_on_a ).

). All the function that are supposed to return a value of a certain type are taking a pointer to that type as first parameter (e.g. function_on_b , but also again create_handle_a )

First of all, I can write a simple wrapper to invoke and check free functions:

template<typename F, typename... Params> void checkVoid(F function, Params... params) { check(function(params...)); }

Which, one could argue, is pretty useless, because writing check(init()) and checkVoid(init) isn’t all that different, and indeed it was a function I wrote last, just for uniformity. The first one I wrote was one for handling the case of a non- void function, i.e. a function that actually returned something (like get_version ):

template<typename F, typename... Params> auto checkReturn(F function, Params... params) { first_ptr_arg_t<F> value{}; checkVoid(function, &value, params...); return value; }

My initial version was actually much more complex (and used the assumption that the F type was a pointer to function), but I received a well-motivated suggestion from Nir Friedman to use the callable syntax on cpplang slack, and this solution turned out to be way simpler (especially for the methods, see below).

first_ptr_type_t is helper defined as:

template<typename T> struct first_ptr_arg {}; template<typename R, typename F, typename... T> struct first_ptr_arg<R(*)(F*, T...)> { using type = F; }; template<typename T> using first_ptr_arg_t = typename first_ptr_arg<T>::type;

In this case, for simplicity, I’m still assuming to receive a function pointer, but I believe this could also be rewritten for generic callables. Since I have an init function to call exactly once, I collected all the API’s free functions into a singleton object:

class API final { API() { checkVoid(init, 0); } public: static const API& instance() { static API _instance{}; return _instance; } int getVersion() const { return checkReturn(get_version); } };

Now, for the objects, I could have started by creating one class per handle type, but since I’m lazy I decided to collect some functionalities in a base class:

template<typename HANDLE> class BaseHandle { using Handle = HANDLE; Handle _handle; public: Handle handle() const noexcept { return _handle; } protected: template<typename F, typename... Params> void methodVoid(F function, Params... params) const { checkVoid(function, params..., handle()); } template<typename F, typename... Params> auto methodReturn(F function, Params... params) const { return checkReturn(function, params..., handle()); } template<typename... Params> explicit BaseHandle(result_type(function)(Handle*, Params...), Params... params) : _handle(checkReturn(function, params...)) {} };

The helpers methodVoid and methodReturn just append the handle at the end of the parameters, and then invoke checkVoid and checkReturn respectively.

In the constructor, the function to create the handle is passed as parameter (again, as a function pointer), but I have a couple of ideas on how to get rid of that.

Now, finally, I can create my wrapper my classes (note that both constructors are also taking an API parameter, just to ensure that the user has called at least once the instance() function, and thus created the API object and called the init function:

class A final : public BaseHandle<handleA> { public: explicit A(int param, const API& = API::instance()) : BaseHandle(create_handle_a, param) {} void function(int param1, float param2) { methodVoid(function_on_a, param1, param2); } }; class B final : public BaseHandle<handleB> { public: explicit B(float param, const API& = API::instance()) : BaseHandle(create_handle_b, param) {} int function(int param) { return methodReturn(function_on_b, param); } ~B() { methodVoid(destroy_handle_b); } };

Easy peasy?

Well, not so easy. Now class B is broken: it can be copied because copy-constructors can’t be inhibited, but it can’t be moved, because move-constructors are inhibited by the presence of the destructor. We actually want the opposite.

I can solve the first part by deleting the copy constructors in B , but defaulting its move constructors manually is not going to fix the movability: the logic in our BaseHandle class would actually be broken, because it doesn’t consider invalidating the source handles. So I should implement some logic in the B class to actually move the handle away and invalidate the state, so the destructor won’t try to destroy the same object twice.

But again, I’m lazy.

Well, turns out in C++17 there’s a pretty nice feature that will fix the movability and the invalidation of handles: std::optional .

[Update 2019-05-24] Well, as user meancoot pointed out in this reddit comment, it turns out the above sentence was entirely false: optional s don’t lose their content when the object contained is moved, as I initially thought. I discovered it independently, being a bit puzzled yesterday when I looked again at Compiler Explorer output, and noticing that the function destroy_handle_b was called twice, without condition. The reason for this behavior is obvious: those handles have copy semantics.

It’s quite clear that doing last-minute changes at 3:00 in the morning is a bad idea, I proposed two fixes in my next post.

template<typename HANDLE> class BaseHandle { // ... std::optional<Handle> _handle; // Update: bug! (read above) public: Handle handle() const { if (_handle.has_value()) { return _handle.value(); } else { throw std::logic_error("trying to obtain an invalid handle"); } } bool valid() const noexcept { return _handle.has_value(); } // ... };

Now, if I, or the user of my wrapper, try to use any method (either methodVoid or methodReturn ) after the handle has been moved away, we will receive a nice logic_error .

What I need to fix in B is just the fact that I shouldn’t try to delete the object if the handle is invalid.

class B final : public BaseHandle<handleB> { // ... ~B() { if (valid()) { methodVoid(destroy_handle_b); } } };

Now, as for the copyability/non-copyability. I could in theory remember that every time I have a destructor, I should also delete a couple of constructors. But again, I’m extremely lazy.

The other option would be making the BaseHandle class non-copyable, but I’d like to keep class A it as it is, both copyable and movable, since it doesn’t need a destructor.

Well, at the end of the day, I come up with a nice utility, that helps in defining the conditional copyability:

template<bool COPYABLE = true> struct Copyable { Copyable() = default; Copyable(const Copyable&) = default; Copyable(Copyable&&) = default; Copyable& operator =(const Copyable&) = default; Copyable& operator =(Copyable&&) = default; ~Copyable() = default; }; template<> struct Copyable<false> { Copyable() = default; Copyable(const Copyable&) = delete; Copyable(Copyable&&) = default; Copyable& operator =(const Copyable&) = delete; Copyable& operator =(Copyable&&) = default; ~Copyable() = default; };

which can in turn be used to make my BaseHandle class conditionally-copyable:

template<typename HANDLE, bool COPYABLE = false> class BaseHandle : Copyable<COPYABLE> { //... };

and then, since it’s non-copyable by default (safer option), I just need to specify, on class A that I want it to be copyable.

class A final : public BaseHandle<handleA, true> { //... };

Note that in case of BaseHandle , I used private inheritance from Copyable, as I want to inherit the properties, and not being able to assign one to the other.

Update 2019-05-23 5:00PM CEST Unfortunately, this would be extremely useful, but doesn’t work in practical cases, such as B , when I want to define a destructor in the class: in this case the presence of a destructor inhibits the automatic creation of a move constructor and move assignment operator, and thus moving fails. Both clang & gcc will try to use the copy constructor, and then produce puzzling errors (see the code on Compiler Explorer).

To make everything work, we need to explicitly default the move constructor and move assignment operator on B :

class B final : public BaseHandle { // ... B(B&&) = default; B& operator =(B&&) = default; };

Final remarks:

Since I know I’m lazy, I ended up writing this in the middle of the night (if I would have stopped at a decent time, I wouldn’t have finished it tomorrow – this is what I did for another three posts, which lie incomplete in my drafts).

For this reason, there might be typos, imprecisions and maybe some blatant mistake. Please add a note in the comments, or send me an email, if you find something odd, I’ll try to fix it as soon as possible.

Like this: Like Loading...