In this post I will describe a technique to solve the problem of making a polymorphic object safe (as in RAII) and copyable. This technique uses a templated constructor and a lambda to store type specific operations which can be used in a later context where the type involved is no longer known.
In C++ we can use inheritance to specialise different types in various ways whilst still store them as the same type. By overriding virtual methods, types that inherit a base type can act in different ways despite being stored together as the same type. Let’s define some polymorphic types to work with.
Now, say that we want to store a few instances of these classes together in a container. To do this we have to declare it as a container of Animals and to avoid object slicing and ensure polymorphic behaviour, they need to be fundamentally represented as either pointers or references. This is a bit of a problem unless we deal with it nicely. An idea is to use references in the container.
However this is no good, the compiler won’t even allow this since references aren’t really assignable. Even if we try to get around this by using std::reference_wrapper
This works for our purpose but this is certainly not safe since we would be required to manually delete the entries. The safety issue is easily addressed using std::unique_ptr, it will even make sure the right destructor is called since we declared it virtual. This gives us the following.
Perfect! At least in terms of safety and polymorphism. We have a way to store our objects and they are managed for us and any calls will be properly forwarded to their actual type.
So. How do we deal with copying these instances? By copying I mean deep copying. This is something which can actually be somewhat non-trivial to solve in a neat way. Looking at our current method of storing our polymorphic objects, std::unique_ptr is non-copyable by design. We can also not just dereference the pointer and copy the contained object since it will be considered a base type even though it might not be. It seems like to be able to copy it we need to know the original type, cast it and manually copy it, then use that and stuff it into a new std::unique_ptr. This doesn’t sound neat though. If we try to use std::shared_ptr instead which is copyable unlike std::unique_ptr, it’s still no good since we’ll just create a shallow copy of the pointer, managing the same object. clearly we need something else.
The most common method I have seen is the following, in one form or another.
This defines a virtual method for deep copying our object. This lets us do instance->deepCopy() to return a brand new std::unique_ptr which manages a new instance which is a proper copy of our object. This solution works, and can arguably work quite well even. However I personally don’t like this solution since it requires me to do quite a bit of boilerplating. I need to define this in every derived type to ensure that it copies properly. I also need to maintain these which is a source of possible bugs. This can be alleviated using various templating methods and such, but I still feel it is not ideal.
Can we do better? Is there no way where we can treat our polymorphic objects like normal objects so that they are properly copied using the language built in copy-mechanics like any other object would without any boilerplating? There are indeed ways of doing this and the technique subject to this post is exactly such a technique.
First we are going to create a templated wrapper type which we can use around our polymorphic types instead of our std::unique_ptr. This wrapper will be very similar to the std::unique_ptr approach except it will also ensure proper copy semantics using the type’s native copy mechanics. We’ll start by defining the class itself along with a constructor for it to be able to construct our polymorphic objects.
This is a good start. It will allows us to declare wrappers by specifying a base type, and construct them by passing a derived type of either an l-value or r-value. std::decay is used to get rid of any reference qualifiers on the passed parameters. Furthermore, a static_assert will make sure that the passed object is actually of a type that subclasses the base type (or is the base type itself) which can prevent misuse through a readable error message.
Next step is to store the provided value. To make it polymorphic we still need it as a pointer or reference as stated previously. I chose to store it as an std::unique_ptr since it deals with all the safety and management for me and I don’t want to reimplement that. So let’s add this!
We forward the given value to store it in our internal std::unique_ptr. Forwarding from the universal reference will be either a copy or a move, as needed.
Adding typical pointer-like accessors to the internal value is necessary to be able to use the object. This is straight forward.
Now we’ve pretty much got a wrapper around an std::unique_ptr which allows us to store and access instances with an inheritance relation to the base class, and any calls made on it will be polymorphic. It’s all safe, but we still can’t copy them. We need to implement a copy constructor/copy assignment operator which properly creates a copy of the contained object. At first this seems difficult since at the time of a copy invocation, we lack the type information of the derived type. How can we trigger the copy mechanics of the derived type in that context?
There is one point where we know the type information of the derived object, and that is in the constructor. The type is known since it is the one that is passed as a parameter. The trick of this article is to utilise a lambda to store the required operation for later. We will create and store a lambda which takes a base pointer and returns a base pointer and internally performs a proper copy.
As you can see, this lambda defines a procedure to copy the content of a std::unique_ptr
Now with the copy routine defined, we just need to make use of it in the copy constructor/assignment. Note that we need to define it for both const and non-const references since otherwise the constructor from above taking by a universal reference will be chosen instead. We’ll also define a move constructor/assigment operator.
Easy! Now we can use this wrapper on any derived class which provides standard copy mechanics and it will work as expected. We can even utilise the rule of zero and we’ll have zero boilerplating when it comes to being able to deep copying these objects. It’s also non-intrusively contained in the wrapper and the other classes can focus solely on what their purposes are.
Here is a bit of example usage.
That’s all for this article! Hope you enjoyed it and maybe even learnt a thing or two. If you have your own take on this problem, or spotted some improvements to make, or flaws with this approach please let me know in a comment!
Thanks for reading!