Not Copying in C++

So, in our last post we had a look at copy construction and copy assignment. Let’s use the code from that post and look at a quick example. Suppose we have simple function that returns a Bond. Something like:

static Bond BondFactory(double rate)
{
        return Bond(rate);
}

Of course this example is very silly, but let’s just go with it. So, suppose we run the following code in a Main method:

Bond b(BondFactory(1.2));

What do we see? Well, the output should look like this:

Constructing

This seems kind of strange. We constructed a Bond object inside the method BondFactory, and we see in the output that we visited the Bond constructor. However, we also copied the return value of BondFactory into a new Bond object b. So why did execution not pass through the copy constructor?

The answer is copy elision. The constructor sees that we are directly copying the result of the BondFactory method into another Bond object. So, rather than constructing and copying, it just constructs the Bond object once, creating the object b directly. What’s surprising here is that our copy constructor has a side effect, it prints to the console, yet the compiler stills skips it.

Now, suppose we defined a subclass of Bond:

class NamedBond : public Bond
{
private:
       string Name;
public:
       NamedBond(string name, double rate) : Bond(bond), Name(name)
       {
             std::cout << "Sub class constructing" << std::endl;
       }
}

And we had a factory method like:

static Bond BondFactory(double rate)
{
      return new NamedBond(rate, "none");
}

Now, if we run the following code in our main method,

Bond b(BondFactory(1.2));

We will see:

Constructing
Sub class constructing
Copy constructing

This time we do pass through the copy constructor. That is because copy elision only happens when the return type is the same as the value it is being copied into. In this case, the return type is a subclass of the value it is being coped into. So the compiler will not elide the copy step.

We will see the same effect when we copy from temporary objects that never get assigned a name. If we run the following code:

Bond b(Bond(1.2));

Our output will be:

Constructing

Whereas, if we run:

Bond b(NamedBond(1.2, "Name"));

we will get:

Constructing
Sub class constructing
Copy constructing

Similarly if we define an addition operator on Bond objects. If we run the following code:

Bond a(1.6);
Bond b(2.3);
Bond c(a + b);

The output will be:

Constructing
Constructing
Constructing

However, if we use the subtype, as so:

NamedBond a(1.6, "Name1");
NamedBond b(2.3, "Name2");
Bond c(a + b);

We will see the copy construction taking place.

Constructing
sub class Constructing
Constructing
sub class Constructing
Constructing
sub class Constructing
Copy constructing

With copy assignment the constructor is a lot stricter. With our example code, the following:

Bond b(1.2);
Bond c(2.3);
b = c;

will result in:

Constructing
Constructing
Copy assigning

So, the copy assignment method gets called here. This is because the compiler only elides the copy assignment when doing so has no consequence. In our case, if it was elided, we would miss the cout statement so it does not. It makes sense to be stricter with elision for the assignment operator, as we may have logic in that method that frees up resources that have already been initialised, this would not be the case with the copy constructor.

The C++ standard allows copy elision to take place, it does not mandate it. This means that elision behaviour depends entirely on what C++ compiler you use, what version of the language you are using and what compiler settings you have enabled. What I have described above is what you should normally expect to see.

Copy elision can also happen when throwing exceptions. However I will not get into the details here, as it is really not as interesting. If your exceptions are defined in such away that copy elision is important, you are defining them wrong.

Leave a Reply

Your email address will not be published. Required fields are marked *