mr-edd.co.uk :: horsing around with the C++ programming language

value_ptr: giving value semantics to polymorphic types

[19th November 2007]

Value semantics are the bread and butter of C++ programming. They're ingrained in the ethos of the standard library. For example, when you add an object to a container, a copy is made using the copy constructor or copy assignment operator, which are of course parts of the core language. This philosophy probably comes from C, where copying primitive types and structs copies their contents. C++ merely extends the concept by providing hooks to make sure copying is done sensibly in situations where the C-style bit-for-bit duplication isn't appropriate.

However, when we start to implement class hierarchies designed to act in a polymorphic fashion (through the use of virtual functions), we often find that we must wave goodbye to the familiar land of value semantics.

In this post, I'll explore why that is and offer up a solution to the problem.

Losing the ability to copy

As I've already mentioned, the copy constructor and copy assignment operators are the lynch pin of value-based programming in C++. We use them to make sure that all the data from the source object is logically replicated in to the destination object.

However, when we're dealing with a polymorphic hierarchy of classes, the copy constructor won't work for us any more. How can we be sure that we're copying all the data from the source object when we can only see a part of it?

To make this discussion a little more concrete, let's look at an example. Suppose we're doing a simulation of the human body at the cellular level. I know diddly-squat about cellular biology so this simulation is completely contrived, but stay with me, here!

class cell
{
    public:
        cell();
        virtual ~cell() { }
        cell(const cell &other); // copy constructor
        cell &operator= (const cell &other); // copy assignment operator

        virtual std::string name() const { return "cell"; }
        // ...

    private:
        cell_guts g_;
};

The keen-eyed among you will have already spotted why the above is going to fail. Let's have a look at how we might implement cell's copy constructor.

cell::cell(const cell &other) :
    g_(other.g_) // copy the guts
{
}

That should be it, right? Hold on though, it looks like cell is going to be the base class in a polymorphic hierarchy. It has a virtual destructor after all. Let's see what happens when we introduce some new classes.

class zygote : public cell
{
    public:
        zygote();
        zygote (const zygote &other);
        zygote &operator= (const zygote &other);

        std::string name() const { return "zygote"; } // overrides cell::name()
        // ...

    private:
        sperm_data s_;
        egg_data e_;
};

class fibroblast : public cell { /* ... */ };
class neuron : public cell { /* ... */ };
// etc ...

We can now see that the cell copy constructor is wrong. Consider the following:

zygote z;
cell c(z); // oops!

On the second line, cell's copy constructor is invoked. It copies out the zygote's g_ member object just fine, but it fails to copy the sperm and egg data out of z_. It just doesn't know they're there! This failure to copy all of the data due to ignorance of the greater object is of course known as slicing.

To work around this problem, you often see people add a virtual clone() function to their hierarchies and persuade people to use that over the usual copying facilities:

// design iteration #2

class cell
{
    public:
        cell();
        virtual ~cell() { }
        virtual cell *clone() const { return new cell; }
        virtual std::string name() const { return "cell"; }

    protected:
        cell(const cell &other) : g_(other.g_) { }

    private:
        cell &operator= (const cell &other); // not implemented

        cell_guts g_;
};

class zygote : public cell
{
    public:
        zygote();
        cell *clone() const; // overrides cell::clone()
        std::string name() const { return "zygote"; } // overrides cell::name()

    protected:
        zygote(const zygote &other) :
            cell(other), // copy the guts
            s_(other.s_), // and the rest this time!
            e_(other.e_)
        {
        }

    private:
        zygote &operator= (const zygote &other); // not implemented

        sperm_data s_;
        egg_data e_;
};

Now the copy constructors are protected. We can't access them in the outside world. We must instead rely on the clone() function to generate a dynamically allocated copy for us. zygote's implementation of clone() might look like this:

cell *zygote::clone() const
{
    // use the copy constructor to do the dirty work
    return new zygote(*this);
}

Now if we're given a reference to a cell, we can call its clone() function to get a copy that hasn't sliced anything in the duplication process.

The problem with cloning

This works just fine but whenever I do anything like this I have a nagging feeling in the back of my mind. Something isn't right, here. In particular, the following things are evident.

First of all this is a workaround at best and a hack at worst. We're providing value semantics through a means that isn't integrated in to the language like copy construction and copy assignment. We're often using the (protected) copy constructor inside of the clone() function, anyway. Writing an extra function is tedious and it's a sad fact that more code means more bugs.

Next, clone() could equally well have been called copy(), replicate() or duplicate(). And I haven't even considered the different naming conventions such as capitalising the first letter of member function names. Indeed, if you use different libraries in your software that have polymorphic hierarchies, you'll often find that they adopt different conventions. This makes it difficult to write generic code.

Lastly, we have to start slinging pointers about the place. This always make me a little bit uneasy. We could of course have our clone() function return something like a boost::shared_ptr<cell> instead of a naked pointer. This would make me a little happier; by using shared_ptrs, we can now copy the cells about without worry of resource leaks for the most part. But shared_ptr encourages reference semantics. We wanted value semantics!

A new smart pointer is born

I started thinking about this issue when a virtual constructors implementation was brought up on the boost developers mailing list. I liked the idea presented, but the solution was too intrusive for my liking — you had to have the objects in your hierarchies derive from a particular base class.

For existing hierarchies, this might be an impossible change to make, especially if you have received them sans source code from a third party vendor.

After a bit of thought, I realised that you could get the same effect without having to impose a base class on a hierarchy[1].

value_ptr, the smart pointer I came up with, makes use of the copy constructor by default. It provides the usual dereferencing operators -> and * that you'd expect of a smart pointer. Let's look at an example. We can now go back to our original cell hierarchy, without the blemish of a clone() function.

cell c;
zygote z;

value_ptr<cell> cp(c); // invokes cell's copy constructor
value_ptr<cell> zp(z); // invokes zygote's copy constructor

value_ptr<cell> cp2(cp); // invokes cell's copy constructor
value_ptr<cell> zp2(zp); // invokes zygote's copy constructor

// value_ptr<>::get() returns the raw pointer.
// Check we really have made a complete copy:
assert(zp.get() != zp2.get());
assert(zp2->name() == "zygote");

Note that even though cp and zp are of the same type, copying them invokes the copy constructor of the underlying dynamic type; If value_ptr<cell> was initialised with a zygote object, it will use the zygote copy constructor forever more. So value_ptr<> allows you to have polymorphic hierarchies with value semantics.

It will also prevent slicing from occurring in the first place:

zygote z;
cell &c = z;

try
{
    // This throws an exception because we can't copy the whole object
    value_ptr<cell> cp(c);
}
catch(const slice_on_copy &ex)
{
    std::cerr << "whoops: " << ex.what() << '\\n';
}

So we're rid of the uncomfortable fudge that is the clone() function (or copy() or duplicate() or …). Instead we get to use our copy constructor — a standardised feature that everyone can provide uniformly in their classes. And of course, we also have our value semantics back!

Interaction with existing hierarchies

This is all well and good, but there are butt-loads of existing hierarchies out there with clone()-like functions that are awkward to treat as value types, even though they want to be. How can value_ptr help with these classes?

If you'll permit me to toot my own horn a little here, the answer is rather elegant. If you define a free function called clone(), value_ptr will pick it up and use it. So suppose we have a hierarchy rooted in a class called monster, which we intend to use in a computer game. Suppose further that it has a duplicate() function which does the same kind of thing as a cell::clone(). We can give it value semantics with value_ptr as follows:

class monster
{
    public:
        virtual monster *duplicate() const = 0;
        // ...
};

// This is what we add to make monster work with value_ptr<>:
inline monster *clone(const monster *m) { return m->duplicate(); }

Now value_ptr will make use of this clone() function, which in turn calls monster::duplicate() when a value_ptr<monster> is copied. This means our ogres, dragons and houndeyes won't get sliced (as much as the game player might wish they were!):

ogre shrek;

value_ptr<monster> mp(shrek);
value_ptr<monster> mp2(mp); // calls clone() and thus monster::duplicate()

In fact, since we're cloning our monster objects, value_ptr won't check for slicing any more, because it should be impossible:

ogre shrek;
monster &m = shrek;

value_ptr<monster> mp(m); // won't throw as before because clone() doesn't slice

You'll note that we didn't have to modify the monster hierarchy at all. value_ptr isn't at all intrusive.

Prior art

This certainly isn't the first time a smart pointer of this kind has been made. But I found the existing solutions that I came across unsatisfactory for one reason or another (though there may be something out there that I didn't find!). In particular, one or more of the following features were missing:

Here are some of the existing implementations that I found:

This list is very much non-exhaustive!

One thing I'd still like to add is support for rvalue move semantics such as those made available by Andrei Alexandrescu's mojo library. This will mean that cloning/copying is only done when absolutely necessary, lessening the amount of dynamic allocation performed.

Conclusion

I hope you agree that value_ptr helps resolve the conflict between value semantics and runtime polymorphic classes in C++. There are a bunch of other features that I haven't mentioned here, such as support for custom allocators and conversions to value_ptrs of related types. You can find out about those over on the dedicated value_ptr page.

Footnotes
  1. you can see the genesis of this line of thought in my posts to the boost list. value_ptr is simply an evolution of those ideas []

Comments

Tony

[28/06/2011 at 17:24:04]

The free clone() function should probably be value_ptr_clone() or some_namespace::clone() or something similar - ie something more unique, to avoid collisions. Same as intrusive_ptr_add.

Edd

[28/06/2011 at 18:45:53]

Hi Tony,

You *should* be able to put clone() and friends in your own class' namespace and ADL will pick it up.

(optional)
(optional)
(required, hint)

Links can be added like [this one -> http://www.mr-edd.co.uk], to my homepage.
Phrases and blocks of code can be enclosed in {{{triple braces}}}.
Any HTML markup will be escaped.