Self Assignment Copy Constructor Pointer

Published by jsmith

Jan 27, 2010 (last update: Aug 20, 2010)

Copy constructors, assignment operators, and exception safe assignment

Score: 4.2/5 (2813 votes)

What is a copy constructor?

A copy constructor is a special constructor for a class/struct that is
used to make a copy of an existing instance. According to the C++
standard, the copy constructor for MyClass must have one of the
following signatures:



Note that none of the following constructors, despite the fact that
they could do the same thing as a copy constructor, are copy
constructors:



or my personal favorite way to create an infinite loop in C++:



When do I need to write a copy constructor?

First, you should understand that if you do not declare a copy
constructor, the compiler gives you one implicitly. The implicit
copy constructor does a member-wise copy of the source object.
For example, given the class:



the compiler-provided copy constructor is exactly equivalent to:



In many cases, this is sufficient. However, there are certain
circumstances where the member-wise copy version is not good enough.
By far, the most common reason the default copy constructor is not
sufficient is because the object contains raw pointers and you need
to take a "deep" copy of the pointer. That is, you don't want to
copy the pointer itself; rather you want to copy what the pointer
points to. Why do you need to take "deep" copies? This is
typically because the instance owns the pointer; that is, the
instance is responsible for calling delete on the pointer at some
point (probably the destructor). If two objects end up calling
delete on the same non-NULL pointer, heap corruption results.

Rarely you will come across a class that does not contain raw
pointers yet the default copy constructor is not sufficient.
An example of this is when you have a reference-counted object.
boost::shared_ptr<> is example.

Const correctness

When passing parameters by reference to functions or constructors, be very
careful about const correctness. Pass by non-const reference ONLY if
the function will modify the parameter and it is the intent to change
the caller's copy of the data, otherwise pass by const reference.

Why is this so important? There is a small clause in the C++ standard
that says that non-const references cannot bind to temporary objects.
A temporary object is an instance of an object that does not have a
variable name. For example:



is a temporary, because we have not given it a variable name. This
is not a temporary:



because the object's name is s.

What is the practical implication of all this? Consider the following:



Many of the STL containers and algorithms require that an object
be copyable. Typically, this means that you need to have the
copy constructor that takes a const reference, for the above
reasons.

What is an assignment operator?

The assignment operator for a class is what allows you to use
= to assign one instance to another. For example:



There are actually several different signatures that an
assignment operator can have:

(1) MyClass& operator=( const MyClass& rhs );
(2) MyClass& operator=( MyClass& rhs );
(3) MyClass& operator=( MyClass rhs );
(4) const MyClass& operator=( const MyClass& rhs );
(5) const MyClass& operator=( MyClass& rhs );
(6) const MyClass& operator=( MyClass rhs );
(7) MyClass operator=( const MyClass& rhs );
(8) MyClass operator=( MyClass& rhs );
(9) MyClass operator=( MyClass rhs );

These signatures permute both the return type and the parameter
type. While the return type may not be too important, choice
of the parameter type is critical.

(2), (5), and (8) pass the right-hand side by non-const reference,
and is not recommended. The problem with these signatures is that
the following code would not compile:



This is because the right-hand side of this assignment expression is
a temporary (un-named) object, and the C++ standard forbids the compiler
to pass a temporary object through a non-const reference parameter.

This leaves us with passing the right-hand side either by value or
by const reference. Although it would seem that passing by const
reference is more efficient than passing by value, we will see later
that for reasons of exception safety, making a temporary copy of the
source object is unavoidable, and therefore passing by value allows
us to write fewer lines of code.

When do I need to write an assignment operator?

First, you should understand that if you do not declare an
assignment operator, the compiler gives you one implicitly. The
implicit assignment operator does member-wise assignment of
each data member from the source object. For example, using
the class above, the compiler-provided assignment operator is
exactly equivalent to:



In general, any time you need to write your own custom copy
constructor, you also need to write a custom assignment operator.

What is meant by Exception Safe code?

A little interlude to talk about exception safety, because programmers
often misunderstand exception handling to be exception safety.

A function which modifies some "global" state (for example, a reference
parameter, or a member function that modifies the data members of its
instance) is said to be exception safe if it leaves the global state
well-defined in the event of an exception that is thrown at any point
during the function.

What does this really mean? Well, let's take a rather contrived
(and trite) example. This class wraps an array of some user-specified
type. It has two data members: a pointer to the array and a number of
elements in the array.



Now, assignment of one MyArray to another is easy, right?



Well, not so fast. The problem is, the line



could throw an exception. This line invokes operator= for type T, which
could be some user-defined type whose assignment operator might throw an
exception, perhaps an out-of-memory (std::bad_alloc) exception or some
other exception that the programmer of the user-defined type created.

What would happen if it did throw, say on copying the 3rd element of 10
total? Well, the stack is unwound until an appropriate handler is found.
Meanwhile, what is the state of our object? Well, we've reallocated our
array to hold 10 T's, but we've copied only 2 of them successfully. The
third one failed midway, and the remaining seven were never even attempted
to be copied. Furthermore, we haven't even changed numElements, so whatever
it held before, it still holds. Clearly this instance will lie about the
number of elements it contains if we call count() at this point.

But clearly it was never the intent of MyArray's programmer to have count()
give a wrong answer. Worse yet, there could be other member functions that
rely more heavily (even to the point of crashing) on numElements being correct.
Yikes -- this instance is clearly a timebomb waiting to go off.

This implementation of operator= is not exception safe: if an exception is
thrown during execution of the function, there is no telling what the state
of the object is; we can only assume that it is in such a bad state (ie,
it violates some of its own invariants) as to be unusable. If the object is
in a bad state, it might not even be possible to destroy the object without
crashing the program or causing MyArray to perhaps throw another exception.
And we know that the compiler runs destructors while unwinding the stack to
search for a handler. If an exception is thrown while unwinding the stack,
the program necessarily and unstoppably terminates.


How do I write an exception safe assignment operator?

The recommended way to write an exception safe assignment operator is via
the copy-swap idiom. What is the copy-swap idiom? Simply put, it is a two-
step algorithm: first make a copy, then swap with the copy. Here is our
exception safe version of operator=:



Here's where the difference between exception handling and exception safety
is important: we haven't prevented an exception from occurring; indeed,
the copy construction of tmp from rhs may throw since it will copy T's.
But, if the copy construction does throw, notice how the state of *this
has not changed, meaning that in the face of an exception, we can guarantee
that *this is still coherent, and furthermore, we can even say that it is
left unchanged.

But, you say, what about std::swap? Could it not throw? Yes and no. The
default std::swap<>, defined in <algorithm> can throw, since std::swap<>
looks like this:



The first line runs the copy constructor of T, which can throw; the
remaining lines are assignment operators which can also throw.

HOWEVER, if you have a type T for which the default std::swap() may result
in either T's copy constructor or assignment operator throwing, you are
politely required to provide a swap() overload for your type that does not
throw. [Since swap() cannot return failure, and you are not allowed to throw,
your swap() overload must always succeed.] By requiring that swap does not
throw, the above operator= is thus exception safe: either the object is
completely copied successfully, or the left-hand side is left unchanged.

Now you'll notice that our implementation of operator= makes a temporary
copy as its first line of code. Since we have to make a copy, we might as
well let the compiler do that for us automatically, so we can change the
signature of the function to take the right-hand side by value (ie, a copy)
rather than by reference, and this allows us to eliminate one line of code:


Before we move into discussing move semantics (the next step on my quest to convert you all to teaching in C++14), let’s clear up something that I often see being taught in a subtly “wrong” way with respect to memory management in even C++98.

Consider the following class, which might be written by a student in a fundamental data structures course:

What we see here is standard fare: we have a class that utilizes dynamic memory (via the pointer), and thus is provides the “Big Three”: a copy constructor, an assignment operator, and a destructor. These three overloads give us the value semantics that we desire, making it so that our class can “blend in” with the built in types of the language and standard library. C++ is rooted in value semantics, so it’s crucial that we get this right so that our classes are well behaved.

But let’s look more closely at our assignment operator. You may have seen it written something like this:

where is a helper function for releasing the memory associated with the current object, and is another helper function that is responsible for creating the memory for a new independent copy of the parameter, assigning it into the current object.

There are actually a couple of problems with this approach.

Pedagogical Complaints

Because the language semantics are often taught very early on in the course, and (at least at UIUC) to fairly inexperienced programmers (through no fault of their own: it’s just their second programming experience in the curriculum in its current form), you have to dance around this issue of the “self assignment” check.

: an enigma for “green” students

is particularly nuanced for students still struggling to understand some of the fundamental differences between Java and C++ (or C and C++). This requires them to understand:

  • the type of (a pointer to the current instance)

  • the purpose of as getting a pointer to the argument (not a reference, and understanding that itself is not a pointer)

  • what it would mean if .

That’s quite a bit of information we’re expecting them to digest in just a short little snippet. But if you write your assignment operator this way, it’s such a critical moment: if they forget this check, they will have memory errors coming out of their ears.

Technical Complaints

However, that’s not the real meat of my argument. My real beef with this setup is that it is completely exception unsafe. And, unless you’re living in a fairytale world where you

  • never allocate to the free store, and
  • never use the standard library

ignoring exceptions will be a fatal mistake at some point in your experience with C++.

And, please don’t come to me claiming that your codebase “doesn’t throw exceptions”, because you and I both know that’s just a load of crock. =)

Nearly every useful program is going to at least do one (and, likely, both) of the above two things. This means you have to care about exceptions.

Exception Safety

So what’s “unsafe” about this code?

Patience, young padawan. Let’s take a step back.

First, let’s identify where exceptions could be thrown, and then define what we want our class to do in the event of an exception. This will define what kind of “exception safety guarantee” we want to provide.

One of the bullet points I made above (when I was being rude; sorry) was that the memory allocator can throw exceptions. How could that be the case? Let’s look at three fairly simple examples:

  • We’re out of memory. This causes a exception to be thrown from the call to that we’ll be using to allocate our array.

  • A constructor for an element in the array throws during the call.

  • The assignment operator for an element in the new array throws when we are copying the data.

So clearly, then, the line that invokes has the potential to throw an exception. What would happen to our data structure in this case? There are a few cases:

  • It could be completely empty if the allocation itself fails (out of memory or a throwing constructor for during the call).

  • It could be partially-copied if the exception came from when copying the data.

So what can we do to deal with this exception?

Let me be clear here: our goal is not to handle the exception. What should the program do if it can no longer allocate heap memory, for example? That’s not something that our data structure should be deciding. So we’re not even going to try to catch and handle this error: instead, what we’re going to try to guarantee is something about the state of our class after the exception has been thrown—namely, that it is in the same state as it was before the assignment line that caused the exception.

Putting the safety back on our assignment operator

Using the template we had before, we could imagine rewriting it in the following way:

Shield your eyes! The horror! The code has exploded, has a horrible block to handle the fact that could throw during the assignment into the array (the cost of a generic type here), and is now almost certainly above the threshold of beginner programmers.

But it is exception safe.

Back to the drawing board

The above monstrosity is clearly beyond what we want to teach. There’s no reason we shouldn’t be able to achieve both goals: the ease of understanding that came with the then version, and also providing the strong exception safety guarantee.

This is where the “copy and swap” idiom comes into play. (It’s worth noting that this idiom is even more useful in C++11/14, but we’ll get there later.)

We start with the following observations:

  • We want to create new memory that is a completely independent copy of the parameter.

  • We must release the memory associated with the current object.

  • The current object must refer to the new memory that we’ve created.

…what if I told you that we already wrote most of this by virtue of having a well defined class? A helper? We have a copy constructor! Let’s see if we can’t use that as a form of “helper function”. Remember from the above code that we want the following chain of events:

  • Allocate memory
  • Copy over values
  • Free old memory
  • Refer to the new copy

and further note that there’s no reason we couldn’t do the last two in a different order (we’d just need a temporary).

Let’s first define a helper function that we’ll use in our implementation:

To be a good citizen, let’s also define a non-member swap function for our class that just delegates to the helper above:

And now consider the following implementation for the assignment operator:

Woah! We have two lines of code. There’s no way that gets us everything we need… right?

But it does.

  • We get the copy by virtue of the argument being passed by value.

  • If the copying fails (e.g., the copy constructor throws), our function is not run at all, so our class appears unchanged by the assignment because it truly didn’t happen.

  • Swapping with the value parameter accomplishes our resource release. Remember that any parameters passed by value are going to have their destructors invoked when the stack frame for that function is popped.

  • We don’t have to check for self assignment anymore, as that code path can now be handled without a special case. Yes, it is less efficient, but the point of checking for self assignment wasn’t as an optimization, it was a “please don’t crash my program” check.

The Catch

The only thing we have to guarantee now is that our copy constructor satisfies the basic exception guarantee (which is to say that it does not leak in the event of an exception), which isn’t too bad (though the code is still not ideal):

The nastiness here is because the marked line (1) could throw during ’s assignment operator.

In the general case, there are ways of avoiding the here, but I think this is a reasonable compromise for now. It’s worth noting at this point that if you were teaching with the then style, your copy constructor was probably exception unsafe, too, so this isn’t just a reflection of some “complication” in the copy-and-swap idiom.

If you’re dealing with some type that you know does not throw from its assignment operator (an assumption I’m willing to make when teaching novice programmers), then the code can be simplified to just:

We’ll revisit this later when we start talking about C++11/14 and show how just a simple language switch can ensure that we get the basic exception guarantee out of our copy constructor in the general case by only a one line change to the above initializer list!

Closing Thoughts: An exception-safe “Big Three” for intro programmers

Let’s recap what our code for the “Big Three” looks like now, including all of our helper functions:

Advantages

  • Real world applicability: exceptions are everwhere, you need to know them and how to handle them

  • Simplified explanation for , using language concepts they’re learning as they are doing copy constructors anyway (pass by value)

  • Elimination of the self assignment check (self assignment is automatically valid in the copy-and-swap idiom)

  • A helper function that’s useful to the outside world:

Disadvantages

  • Requires some discussion of what exceptions are, what they are used for, and why we care about them

  • If you are truly being careful, in C++98/03 you will need to have a block in the copy constructor (but not in C++11/14, more to come…)

Coming Up

Now that we know about the copy-and-swap idiom, in the next post I’m going to talk briefly about move semantics in C++11/14, and then we can move on to tackle what I teased at in the very first post in this series: that we can teach manual memory management in C++11/14 without losing out on any teaching opportunities compared to C++98/03, all the while simultaneously being more modern and encouraging students to write safe code.

Yell at me in the comments!

0 Replies to “Self Assignment Copy Constructor Pointer”

Lascia un Commento

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *