The latest version of this topic can be found at Copy Constructors and Copy Assignment Operators (C++).
Starting in C++11, two kinds of assignment are supported in the language: copy assignment and move assignment. In this article "assignment" means copy assignment unless explicitly stated otherwise. For information about move assignment, see Move Constructors and Move Assignment Operators (C++).
Both the assignment operation and the initialization operation cause objects to be copied.
Assignment: When one object's value is assigned to another object, the first object is copied to the second object. Therefore,
causes the value of to be copied to .
Initialization: Initialization occurs when a new object is declared, when arguments are passed to functions by value, or when values are returned from functions by value.
You can define the semantics of "copy" for objects of class type. For example, consider this code:
The preceding code could mean "copy the contents of FILE1.DAT to FILE2.DAT" or it could mean "ignore FILE2.DAT and make a second handle to FILE1.DAT." You must attach appropriate copying semantics to each class, as follows.
By using the assignment operator together with a reference to the class type as the return type and the parameter that is passed by reference—for example .
By using the copy constructor. For more information about the copy constructor, see Rules for Declaring Constructors.
If you do not declare a copy constructor, the compiler generates a member-wise copy constructor for you. If you do not declare a copy assignment operator, the compiler generates a member-wise copy assignment operator for you. Declaring a copy constructor does not suppress the compiler-generated copy assignment operator, nor vice versa. If you implement either one, we recommend that you also implement the other one so that the meaning of the code is clear.
Member-wise assignment is covered in more detail in (NOTINBUILD) Memberwise Assignment and Initialization.
The copy constructor takes an argument of type class-name&, where class-name is the name of the class for which the constructor is defined. For example:
Make the type of the copy constructor's argument const class-name& whenever possible. This prevents the copy constructor from accidentally changing the object from which it is copying. It also enables copying from const objects.
Compiler-generated copy constructors, like user-defined copy constructors, have a single argument of type "reference to class-name." An exception is when all base classes and member classes have copy constructors declared as taking a single argument of type constclass-name&. In such a case, the compiler-generated copy constructor's argument is also const.
When the argument type to the copy constructor is not const, initialization by copying a const object generates an error. The reverse is not true: If the argument is const, you can initialize by copying an object that is not const.
Compiler-generated assignment operators follow the same pattern with regard to const. They take a single argument of type class-name& unless the assignment operators in all base and member classes take arguments of type constclass-name&. In this case, the class's generated assignment operator takes a const argument.
When virtual base classes are initialized by copy constructors, compiler-generated or user-defined, they are initialized only once: at the point when they are constructed.
The implications are similar to those of the copy constructor. When the argument type is not const, assignment from a const object generates an error. The reverse is not true: If a const value is assigned to a value that is not const, the assignment succeeds.
For more information about overloaded assignment operators, see Assignment.
Special Member Functions
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.
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.
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.
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 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:
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:
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…)
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!