The Copy/Move Constructor - Introduction to Classes - C++ For Dummies (2014)

C++ For Dummies (2014)

Part III

Introduction to Classes

Chapter 17

The Copy/Move Constructor

In This Chapter

arrow Introducing the copy/move constructor

arrow Making copies

arrow Having copies made for you automatically

arrow Creating shallow copies versus deep copies

arrow Avoiding all those copies with a move constructor

The constructor is a special function that C++ invokes automatically when an object is created to allow the object to initialize itself. Chapter 15 introduces the concept of the constructor, whereas Chapter 16 describes other types of constructors. This chapter examines two particular variations of the constructor known as the copy and move constructors.

Copying an Object

A copy constructor is the constructor that C++ uses to make copies of objects. It carries the name X::X(const X&), where X is the name of the class. That is, it’s the constructor of class X, which takes as its argument a reference to an object of class X. Now, I know that this sounds really useless, but just give me a chance to explain why C++ needs such beasties.

image The move constructor is unique to C++ 2011. Most of this chapter concerns the copy constructor. I present the details of the move constructor towards the end of this chapter.

Why you need the copy constructor

Think for a moment about what happens when you call a function like the following:

void fn(Student fs)
{
// ...same scenario; different argument...
}
int main(int argcs, char* pArgs[])
{
Student ms;
fn(ms);
return 0;
}

In the call to fn(), C++ passes a copy of the object ms and not the object itself.

Now consider what it means to create a copy of an object. First, it takes a constructor to create an object, even a copy of an existing object. C++ could create a default copy constructor that copies the existing object into the new object one byte at a time. That’s what older languages such as C do. But what if the class doesn't want a simple copy of the object? What if something else is required? (Ignore the “why?” for a little while.) The class needs to be able to specify exactly how the copy should be created.

Thus, C++ uses a copy constructor in the preceding example to create a copy of the object ms on the stack during the call of function fn(). This particular copy constructor would be Student::Student(Student&) — say that three times quickly.

Using the copy constructor

The best way to understand how the copy constructor works is to see one in action. Consider the following CopyConstructor program:

// CopyConstructor - demonstrate a copy constructor
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Student
{
public:
// conventional constructor
Student(const char *pName = "no name", int ssId = 0)
: name(pName), id(ssId)
{ cout << "Constructed " << name << endl; }

// copy constructor
Student(const Student& s)
: name("Copy of " + s.name), id(s.id)
{ cout << "Constructed " << name << endl; }

~Student() { cout << "Destructing " << name << endl; }

protected:
string name;
int id;
};

// fn - receives its argument by value
void fn(Student copy)
{
cout << "In function fn()" << endl;
}

int main(int nNumberofArgs, char* pszArgs[])
{
Student scruffy("Scruffy", 1234);
cout << "Calling fn()" << endl;
fn(scruffy);
cout << "Back in main()" << endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

The output from executing this program appears as follows:

Constructed Scruffy
Calling fn()
Constructed Copy of Scruffy
In function fn()
Destructing Copy of Scruffy
Back in main()
Press Enter to continue...

The normal Student constructor generates the first message from the declaration on the first line of main() about creating scruffy. main() then outputs the Calling … message before calling fn(). As part of the function call process, C++ invokes the copy constructor to make a copy of scruffyto pass to fn(). The copy constructor prepends the string “Copy of” to the student's name before displaying it on the console. The function fn() outputs the In function … message. The copied Student object copy is destructed at the return from fn(). (You can tell it's the copy because of the “Copy of” prepended to the front.) The original object, scruffy, is destructed at the end of main().

The Automatic Copy Constructor

Like the default constructor, the copy constructor is important; important enough that C++ thinks no class should be without one. If you don’t provide your own copy constructor, C++ generates one for you. (This differs from the default constructor that C++ provides unless your class has constructors defined for it.)

The copy constructor provided by C++ performs a member-by-member copy of each data member. You can see this in the following DefaultCopyConstructor program. (I left out the definition of the Student class to save space — it's identical to that shown in the CopyConstructor program. The entire DefaultCopyConstructor program is available online at www.dummies.com/extras/cplusplus.)

class Tutor
{
public:
Tutor(Student& s)
: student(s), id(0)
{ cout << "Constructing Tutor object" << endl; }
protected:
Student student;
int id;
};

void fn(Tutor tutor)
{
cout << "In function fn()" << endl;
}

int main(int argcs, char* pArgs[])
{
Student scruffy("Scruffy");
Tutor tutor(scruffy);
cout << "Calling fn()" << endl;
fn(tutor);
cout << "Back in main()" << endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

Executing this program generates the following output:

Constructed Scruffy
Constructed Copy of Scruffy
Constructing Tutor object
Calling fn()
Constructed Copy of Copy of Scruffy
In function fn()
Destructing Copy of Copy of Scruffy
Back in main()
Press Enter to continue...

Destructing Copy of Scruffy
Destructing Scruffy

Constructing the scruffy object generates the first output message from the “plain Jane” constructor. The constructor for the tutor object invokes the Student copy constructor to generate its own Student data member and then outputs its own message. This accounts for the next two lines of output.

The program then passes a copy of the Tutor object to the function fn(). Because the Tutor class does not define a copy constructor, the program invokes the default copy constructor to make a copy to pass to fn().

The default Tutor copy constructor invokes the copy constructor for each data member. The copy constructor for int does nothing more than copy the value. You've already seen how the Student copy constructor works. This is what generates the Constructed Copy of Copy of Scruffymessage. The destructor for the copy is invoked as part of the return from function fn(). The final destructors are invoked when the program returns from main().

Creating Shallow Copies versus Deep Copies

Performing a member-by-member copy seems the obvious thing to do in a copy constructor. Other than adding the capability to tack on silly things such as Copy of to the front of students’ names, when would you ever want to do anything but a member-by-member copy?

Consider what happens if the constructor allocates an asset, such as memory off the heap. If the copy constructor simply makes a copy of that asset without allocating its own asset, you end up with a troublesome situation: two objects thinking they have exclusive access to the same asset. This becomes nastier when the destructor is invoked for both objects and they both try to put the same asset back. To make this more concrete, consider the following example class:

// ShallowCopy - performing a byte-by-byte (shallow) copy
// is not correct when the class holds assets
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Person
{
public:
Person(const char *pN)
{
cout << "Constructing " << pN << endl;
pName = new string(pN);
}
~Person()
{
cout << "Destructing " << pName
<< " (" << *pName << ")" << endl;
*pName = "already destructed memory";
// delete pName;
}
protected:
string *pName;
};

void fn()
{
// create a new object
Person p1("This_is_a_very_long_name");

// copy the contents of p1 into p2
Person p2(p1);
}

int main(int argcs, char* pArgs[])
{
cout << "Calling fn()" << endl;
fn();
cout << "Back in main()" << endl;

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

This program generates the following output:

Calling fn()
Constructing This_is_a_very_long_name
Destructing 0x3f2bb8 (This_is_a_very_long_name)
Destructing 0x3f2bb8 (already destructed memory)
Back in main()
Press Enter to continue...

The constructor for Person allocates memory off the heap to store the person’s name. The destructor would normally return this memory to the heap using the delete keyword; however, in this case, I've replaced the call to delete with a statement that replaces the name with a message. The main program calls the function fn(), which creates one person, p1, and then makes a copy of that person, p2. Both objects are destructed automatically when the program returns from the function.

Only one constructor output message appears when this program is executed. That’s not too surprising because the C++-provided copy constructor used to build p2 performs no output. The Person destructor is invoked twice, however, as both p1 and p2 go out of scope. The first destructor outputs the expected This_is_a_very_long_name. The second destructor indicates that the memory has already been deleted. Notice also that the address of the memory block is the same for both objects (0x3F2BB8).

image If the program really were to delete the name, the program would become unstable after the second delete and might not even complete properly without crashing.

The problem is shown graphically in Figure 17-1. The object p1 is copied into the new object p2, but the assets are not. Thus, p1 and p2 end up pointing to the same assets (in this case, heap memory). This is known as a shallow copy because it just “skims the surface,” copying the members themselves.

image

Figure 17-1: Shallow copy of p1 to p2.

The solution to this problem is demonstrated visually in Figure 17-2. This figure represents a copy constructor that allocates its own assets to the new object.

image

Figure 17-2: Deep copy of p1 to p2.

The following shows an appropriate copy constructor for class Person, the type you've seen up until now. (This class is embodied in the program DeepCopy, which is on this book's online material at www.dummies.com/extras/cplusplus.)

class Person
{
public:
Person(const char *pN)
{
cout << "Constructing " << pN << endl;
pName = new string(pN);
}
Person(Person& person)
{
cout << "Copying " << *(person.pName) << endl;
pName = new string(*person.pName);
}
~Person()
{
cout << "Destructing " << pName
<< " (" << *pName << ")" << endl;
*pName = "already destructed memory";
// delete pName;
}
protected:
string *pName;
}

Here you see that the copy constructor allocates its own memory block for the name and then copies the contents of the source object name into this new name block. This is a situation similar to that shown in Figure 17-2. Deep copy is so named because it reaches down and copies all the assets. (Okay, the analogy is pretty strained, but that’s what they call it.)

The output from this program is as follows:

Calling fn()
Constructing This_is_a_very_long_name
Copying This_is_a_very_long_name
Destructing 0x9f2be0 (This_is_a_very_long_name)
Destructing 0x9f2ba0 (This_is_a_very_long_name)
Back in main()
Press Enter to continue...

The destructor for Person now indicates that the string pointers in p1 and p2 don't point to the same block of memory: the addresses of the two objects are different, and the name in the version owned by the copy has not been overwritten indicating that it's been deleted.

image The real ~Person destructor should delete pName.

It’s a Long Way to Temporaries

Passing arguments by value to functions is the most obvious but not the only example of the use of the copy constructor. C++ creates a copy of an object under other conditions as well.

Consider a function that returns an object by value. In this case, C++ must create a copy using the copy constructor. This situation is demonstrated in the following code snippet:

Student fn(); // returns object by value
int main(int argcs, char* pArgs[])
{
Student s;
s = fn(); // call to fn() creates temporary

// how long does the temporary returned by fn()last?
return 0;
}

The function fn() returns an object by value. Eventually, the returned object is copied to s, but where does it reside until then?

C++ creates a temporary object into which it stuffs the returned object. “Okay,” you say. “C++ creates the temporary, but how does it know when to destruct it?” Good question. In this example, it doesn’t make much difference because you’ll be through with the temporary when the copy constructor copies it into s. But what if s is defined as a reference? It makes a big difference how long temporaries live because refS exists for the entire function:

int main(int argcs, char* pArgs[])
{
Student& refS = fn();
// ...now what?...
return 0;
}

Temporaries created by the compiler are valid throughout the extended expression in which they were created and no further.

In the following function, I mark the point at which the temporary is no longer valid:

Student fn1();
int fn2(Student&);
int main(int argcs, char* pArgs[])
{
int x;
// create a Student object by calling fn1().
// Pass that object to the function fn2().
// fn2() returns an integer that is used in some
// silly calculation.
// All this time the temporary returned from fn1()
// remains valid.
x = 3 * fn2(fn1()) + 10;

// the temporary returned from fn1() is now no longer valid
// ...other stuff...
return 0;
}

This makes the reference example invalid because the object may go away before refS does, leaving refS referring to a non-object.

Avoiding temporaries, permanently

It may have occurred to you that all this copying of objects hither and yon can be a bit time-consuming. What if you don’t want to make copies of everything? The most straightforward solution is to pass objects to functions and return objects from functions by reference. Doing so avoids the majority of temporaries.

But what if you’re still not convinced that C++ isn’t out there craftily constructing temporaries that you know nothing about? Or what if your class allocates unique assets that you don’t want copied? What do you do then?

You can add an output statement to your copy constructor. The presence of this message when you execute the program warns you that a copy has just been made.

A more clever approach is to declare the copy constructor protected, as follows:

class Student
{
protected:
Student(Student&s){}

public:
// ...everything else normal...
};

image The C++ '11 standard also allows the programmer to delete the copy constructor:

class Student
{
Student(Student&s) = delete;

// ...everything else normal...
};

Either declaring the copy constructor protected or deleting it entirely precludes any external functions, including C++, from constructing a copy of your Student objects. If no one can invoke the copy constructor, no copies are being generated. Voilà.

The move constructor

Under certain conditions, C++ can create a copy of an object that is used only for the duration of a single statement. Such objects, known as temporaries, are destructed as soon as the expression is completed. It doesn't make sense to make copies of temporary objects that are about to be destructed anyway.

image C++ '11 allows the programmer to create a constructor known as a move constructor that simply moves assets from the source to the destination rather than making unnecessary copies. Move constructors have the format X::X(X&&). This is a new use of “&&”.

Consider the following highly contrived example.

image C++ '11 includes several return optimizations to avoid the creation of unnecessary copies of objects which this example has to defeat to demonstrate the move constructor. You'll see much less contrived examples in the discussion of overloading operators in Chapter 22.

//
// MoveCopy - demonstrate the principle of moving a
// temporary rather than creating a copy
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Person
{
public:
Person(const char *pN)
{
pName = new string(pN);
cout << "Constructing " << *pName << endl;
}
Person(Person& p)
{
cout << "Copying " << *p.pName << endl;
pName = new string("Copy of ");
*pName += *p.pName;
}
Person(Person&& p)
{
cout << "Moving " << *p.pName << endl;
pName = p.pName;
p.pName = nullptr;
}
~Person()
{
if (pName)
{
cout << "Destructing " << *pName << endl;
delete pName;
}
else
{
cout << "Destructing null object" << endl;
}
}
protected:
string* pName;
};

Person fn2(Person p)
{
cout << "Entering fn2" << endl;
return p;
}

Person fn1(char* pName)
{
cout << "Entering fn1_ << endl;
return fn2(*new Person(pName));
}

int main(int argcs, char* pArgs[])
{
Person s(fn1("Scruffy"));

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
return 0;
}

Notice how the move constructor assigns the pName pointer from the source object p and then zeroes out that pointer so that the destructor does not return the memory when the temporary is destructed. This is much more efficient than allocating yet another string object off of the heap and copying the contents of p.pName to this new string.

The output from this program appears as follows:

Entering fn1
Constructing Scruffy
Copying Scruffy
Entering fn2
Moving Copy of Scruffy
Destructing null object
Press Enter to continue...

Destructing Copy of Scruffy

In this case, fn1() creates a Person object. It then copies this object in the call to fn2() using the copy constructor. The function fn2() does nothing more than return a copy of this object to fn1(); however, this copy is just a temporary object that fn1() returns to main(). Rather than use the copy constructor to create a “Copy of copy of Scruffy,” C++ '11 invokes the move constructor to take the contents of the temporary object. When this temporary is subsequently destructed, it is a “null object” because its pName has been taken away and reassigned.

image You don't have to create a move constructor. The program would have worked just fine, albeit a hair slower, with just the copy constructor. Move constructors should be considered an advanced topic. You will see examples that are less contrived in Chapter 22.