Copy Constructor Self Assignment C++ Array

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!

C++ allows programmers to specify how operators work with objects of new class types--a concept known as operator overloading. One example of an overloaded operator built into C++ is <<, which is used both as the stream insertion operator and as the bitwise left-shift operator. Similarly, >> is used as both the stream extraction operator and as the bitwise right-shift operator.

This tutorial discusses an Array class that overloads several operators. Our Array class provides enhanced functionality over traditional C++ arrays, such as assigning and comparing Array objects, and checking array indices to ensure that we do not access elements outside the bounds of the underlying C++ array. In addition, this tutorial introduces a copy constructor for initializing a new Array object with the contents of an existing Array object. This tutorial is intended for students and professionals who are familiar with basic array, pointer and class concepts in C++.

Download the code examples for this tutorial.

[Note: This tutorial is an excerpt (Section 11.8) of Chapter 11, Operator Overloading, from our textbook C++ How to Program, 5/e. These tutorials may refer to other chapters or sections of the book that are not included here. Permission Information: Deitel, Harvey M. and Paul J., C++ HOW TO PROGRAM, ©2005, pp.582-593. Electronically reproduced by permission of Pearson Education, Inc., Upper Saddle River, New Jersey.]

11.8 Case Study: Class (Continued)

1 2 3 #include <iostream> 4 using std::cerr; 5 using std::cout; 6 using std::cin; 7 using std::endl; 8 9 #include <iomanip> 10 using std::setw; 11 12 #include <cstdlib> 13 using std::exit; 14 15 #include "Array.h" 16 17 18 Array::Array( int arraySize ) 19 { 20 size = ( arraySize > 0 ? arraySize : 10 ); 21 ptr = new int[ size ]; 22 23 for ( int i = 0; i < size; i++ ) 24 ptr[ i ] = 0; 25 } 26 27 28 29 Array::Array( const Array &arrayToCopy ) 30 : size( arrayToCopy.size ) 31 { 32 ptr = new int[ size ]; 33 34 for ( int i = 0; i < size; i++ ) 35 ptr[ i ] = arrayToCopy.ptr[ i ]; 36 } 37 38 39 Array::~Array() 40 { 41 delete [] ptr; 42 } 43 44 45 int Array::getSize() const 46 { 47 return size; 48 } 49 50 51 52 const Array &Array::operator=( const Array &right ) 53 { 54 if ( &right != this ) 55 { 56 57 58 if ( size != right.size ) 59 { 60 delete [] ptr; 61 size = right.size; 62 ptr = new int[ size ]; 63 } 64 65 for ( int i = 0; i < size; i++ ) 66 ptr[ i ] = right.ptr[ i ]; 67 } 68 69 return *this; 70 } 71 72 73 74 bool Array::operator==( const Array &right ) const 75 { 76 if ( size != right.size ) 77 return false; 78 79 for ( int i = 0; i < size; i++ ) 80 if ( ptr[ i ] != right.ptr[ i ] ) 81 return false; 82 83 return true; 84 } 85 86 87 88 int &Array::operator[]( int subscript ) 89 { 90 91 if ( subscript < 0 || subscript >= size ) 92 { 93 cerr << "\nError: Subscript " << subscript 94 << " out of range" << endl; 95 exit( 1 ); 96 } 97 98 return ptr[ subscript ]; 99 } 100 101 102 103 int Array::operator[]( int subscript ) const 104 { 105 106 if ( subscript < 0 || subscript >= size ) 107 { 108 cerr << "\nError: Subscript " << subscript 109 << " out of range" << endl; 110 exit( 1 ); 111 } 112 113 return ptr[ subscript ]; 114 } 115 116 117 118 istream &operator>>( istream &input, Array &a ) 119 { 120 for ( int i = 0; i < a.size; i++ ) 121 input >> a.ptr[ i ]; 122 123 return input; 124 } 125 126 127 ostream &operator<<( ostream &output, const Array &a ) 128 { 129 int i; 130 131 132 for ( i = 0; i < a.size; i++ ) 133 { 134 output << setw( 12 ) << a.ptr[ i ]; 135 136 if ( ( i + 1 ) % 4 == 0 ) 137 output << endl; 138 } 139 140 if ( i % 4 != 0 ) 141 output << endl; 142 143 return output; 144 }

Fig. 11.7 class member- and -function definitions.

Page 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10

Tutorial Index

0 Thoughts to “Copy Constructor Self Assignment C++ Array

Leave a comment

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