In This Chapter
Introducing the copy constructor
Making copies
Having copies made for you automatically
Creating shallow copies versus deep copies
Avoiding all those copies
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 a particular variation of the constructor known as the copy constructor.
A copy constructor is the constructor that C++ uses to make copies of objects. It carries the name X::X(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 a beastie.
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.
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(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 chester("Chester", 1234); cout << "Calling fn()" << endl; fn(chester); cout << "Back in main()" << endl; // wait until user is ready before terminating program // to allow the user to see the program results system("PAUSE"); return 0; }
The output from executing this program appears as follows:
Constructed Chester Calling fn() Constructed Copy of Chester In function fn() Destructing Copy of Chester Back in main() Press any key to continue ...
The normal Student
constructor generates the first message from the declaration on the first line of main()
about creating chester. 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 chester
to pass to fn()
. The copy constructor outputs a message. The function fn()
outputs the In function... message
. The copied Student
object copy
is destructed at the return from fn()
. The original object, chester
, is destructed at the end of main()
.
The assignment operator is defined on the string
class to concatenate two strings or a string and a null-terminated character string. You see how to define operators on user-defined classes in Chapter 22.
The copy constructor copies the name of the object provided it is prepended with "Copy of" into its own name field. It then copies the id
field from s
into the id
field of the current object. The constructor outputs the resulting name field before returning.
The first line of output shows the chester
object being created. The third line demonstrates the copy Student
being generated from the copy constructor in the call to fn()
. The function fn()
does nothing more than output a message. The copy is destructed as part of the return, which generates the destructing...
message.
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. The copy constructor that early versions of C++ provided performed a bit-wise copy. The difference is that a member-by-member copy invokes all copy constructors that might exist for the members of the class, whereas a bit-wise copy does not. You can see the effects of this difference in the following DefaultCopyConstructor sample 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 on the enclosed CD-ROM.)
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 chester("Chester"); Tutor tutor(chester); 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 system("PAUSE"); return 0; }
Executing this program generates the following output:
Constructed Chester Constructed Copy of Chester Constructing Tutor object Calling fn() Constructed Copy of Copy of Chester In function fn() Destructing Copy of Copy of Chester Back in main() Press any key to continue...
Constructing the chester
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 Chester
message. The destructor for the copy is invoked as part of the return from function fn()
.
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 system("PAUSE"); return 0; }
This program generates the following output:
Calling fn() Constructing This_is_a_very_long_name Destructing 0x360f78 (This_is_a_very_long_name) Destructing 0x360f78 (already destructed memory) Back in main() Press any key 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 replace 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 (0x360F78).
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.
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.
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 CD-ROM.)
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 0x270fb0 (This_is_a_very_long_name) Destructing 0x270f60 (This_is_a_very_long_name) Back in main() Press any key 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.
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 nonobject.
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 crafty approach is to declare the copy constructor protected, as follows:
class Student { protected: Student(Student&s){} public: // ...everything else normal... };
The C++ '09 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à.
3.12.76.164