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:

class Interface
{
public:
    virtual void doSomething() const = 0;
    virtual ~Interface() = default;
};

With one the classes implementing this interface:

class Implementation : public Interface
{
public:
    virtual void doSomething() const override
    {
        /* ... */
    }
};

How to make a copy of the Implementation object?

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

Implementation x = // ...
Implementation y = x;

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

Interface& x = // ...
Interface& y = ??

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:

class Interface
{
public:
    virtual Interface* clone() const = 0;

    virtual void doSomething() const = 0;
    virtual ~Interface() = default;
};

and on the implementation’s side:

class Implementation : public Interface
{
public:
    virtual Implementation* clone() const override
    {
        return new Implementation(*this);
    }

    virtual void doSomething() const override
    {
        /* ... */
    }
};

(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:

Interface& x = // ...
Interface* y = x.clone();

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:

class Interface
{
public:
    virtual std::unique_ptr<Interface> clone() const = 0;

    virtual void doSomething() const = 0;
    virtual ~Interface() = default;
};

class Implementation : public Interface
{
public:
    virtual std::unique_ptr<Interface> clone() const override
    {
        return std::make_unique<Implementation>(*this);
    }

    virtual void doSomething() const override
    {
        /* ... */
    }
};

And at call site:

Interface& x = // ...
std::unique_ptr<Interface> y = x.clone();

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 cppreference.com:

// note: this implementation does not disable this overload for array types
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

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:

class Interface1
{
public:
    virtual Interface1* clone() const = 0;
    virtual void doSomething() const = 0;
    virtual ~Interface1() = default;
};

class Interface2
{
public:
    virtual Interface2* clone() const = 0;
    virtual void doSomethingElse() const = 0;
    virtual ~Interface2() = default;
};

class Implementation : public Interface1, public Interface2
{
public:
    virtual Implementation* clone() const override
    {
        return new Implementation(*this);
    }
    virtual void doSomething() const override
    {
        /* ... */
    }
    virtual void doSomethingElse() const override
    {
        /* ... */
    }
};

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:

class Interface1
{
public:
    virtual std::unique_ptr<Interface1> cloneInterface1() const = 0;
    virtual void doSomething() const = 0;
    virtual ~Interface1() = default;
};

class Interface2
{
public:
    virtual std::unique_ptr<Interface2> cloneInterface2() const = 0;
    virtual void doSomethingElse() const = 0;
    virtual ~Interface2() = default;
};

class Implementation : public Interface1, public Interface2
{
public:
    virtual std::unique_ptr<Interface1> cloneInterface1() const override
    {
        return make_unique<Implementation>(*this);
    }
    virtual std::unique_ptr<Interface2> cloneInterface2() const override
    {
        return make_unique<Implementation>(*this);
    }
    virtual void doSomething() const override
    {
        
    }
    virtual void doSomethingElse() const override
    {
        
    }
};

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:

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed