Jonathan Boccara's blog

Polymorphic clones in modern C++

Published September 8, 2017 - 3 Comments

How to copy an object that is accessible only by an interface that it implements?

This question has been around for a very long time, and is associated with a classical solution described by Scott Meyers in Item 25 of More Effective C++. This solution still works, but can benefit from modern C++ features that weren’t in the standard when the book came out, in particular smart pointers.

I’m going to go over a quick reminder of the problem and the classical solution, and then show how throwing a bunch of smart pointers in the party can make the code more robust and more expressive, if we agree on certain conventions.

This post is part of the series Smart Developers Use Smart Pointers:

The classical problem

Let’s take the example of the following interface:

With one the classes implementing this interface:

How to make a copy of the Implementation object?

If you have access to the object itself, there is nothing easier:

But the existence of the Interface suggests that there are polymorphic contexts where the object is accessible only via the interface:

And there is a problem here because,  in C++, to construct an object we must spell out in the code the actual type of the object to be constructed (except in the case of implicit conversions). And here we don’t know what this type is. It could be Implementation, or any other class inheriting from Interface.

And even if, for some reason, we knew for sure that it was an Implementation, the calling code may not have access to this class, which is one of the purposes of having an interface in the first place.

What to do then?

The classical solution

The classical solution is to “virtualize” the constructor, as Scott Meyers puts it. That is to say add a clone method in the interface, that delegates the object construction to the implementation itself. The interface then looks like:

and on the implementation’s side:

(override wasn’t in the original solution, since it appeared in C++11, but it’s still a good practice to use it).

So the copy of the interface at call site looks like this:

Notice that the return type of the clone method differ between the interface in the implementation. It is because C++ allows overriding a virtual method with one that has a different return type, provided this return type is a pointer (resp. reference) to a class convertible to the one pointed to (resp. referenced by) the return type of the base class. This is called covariance.

This technique allows the desired copy, but exhibits another classical problem: the call site receives the responsibility to delete the cloned object, but nothing ensures that it will do it. Particularly if there is an early return or an exception thrown further down the code, the object has a risk to leak.

A modern solution

The tool cut out for solving this problem are smart pointers, and in particular std::unique_ptr.

The idea is to make the clone function return a unique_ptr, that will take care about deleting the new object in all situations. Here is how to adapt the code with this:

And at call site:

Let’s look at this solution more closely.

First, your compiler may not have std::make_unique since it arrived in C++14 while std::unique_ptr only came in C++11 (I believe this was just an oversight). If so, you can use this implementation proposed by

Second, and much more annoyingly, the covariance doesn’t hold any more, because the clone method is no longer returning pointers. It now has to return an std::unique_ptr<Interface> in the interface AND in the implementation.

In the above case it doesn’t cause any practical problem, given that Implementation already depends on Interface anyway. But let’s consider the case where an implementation inherits from several interfaces. The solution without smart pointers scales effortlessly because the clone method is independent from the interface:

But with smart pointers, the situation is different: the clone method, bound to Interface1, cannot be used for Interface2! And since the clone method doesn’t take any argument, there is no way to add a new overload returning a unique_ptr to Interface2.

One solution that comes to mind is to use template methods. But there is no such such thing as a template virtual method so this solution is off the table.

Another idea would be to isolate the clone method in a clonable interface. But this would force the call site to dynamic_cast back and forth from the real interface to the clonable interface. Not good either.

Clearing the ambiguity

The alternative I would suggest is to use different names for the clone methods in the interfaces.

The code would then look like:

But to be viable, this solution has to rely on a guideline for interface designers: if you choose to implement a clone method that returns a smart pointer, then don’t call it just clone.

Rather, use a specific name, like cloneInterfaceX, that won’t conflict with the copy functions coming from the other interfaces.

This way, you allow implementers to use your interface even if they already use others.

As Aristotle would have it, Man is a social animal. Let us developers take example and let our interfaces live together without getting in conflict with each other, and die with dignity, that is, by being sure to be called on their destructors.

Now this is a solution for this particular problem, but there is a bigger C++ question behind this: how to make smart pointers work with covariance? You will have the answer on the next post, written by Raoul Borges who’s much more experienced than me on that question.

Related articles:

Become a Patron!
Share this post! Facebooktwittergoogle_pluslinkedin    Don't want to miss out ? Follow:   twitterlinkedinrss

Get a free ebook on C++ smart pointers

Get a free ebook of more than 50 pages that will teach you the basic, medium and advanced aspects of C++ smart pointers, by subscribing to our mailing list! On top of that, you will also receive regular updates to make your code more expressive.

No spam. You can unsubscribe at any time.