Fully understanding the copy constructor may require reviewing previously introduced concepts:
Three common operations cause a C++ program to copy an object:
The operations are necessary and frequently occur in C++ programs, so the compiler automatically provides two object-copying functions: an assignment operator and a copy constructor. (We'll see how to implement the assignment operator as a function in the next chapter.) The compiler-created functions are sufficient in many but not all cases. Programmers must override both functions when the compiler-created versions are insufficient. The following guidelines, based on our earlier definitions of "simple" and "complex" classes, clarify when the overrides are necessary:
To understand which functions we need to override, we must first understand which C++ statements copy an object and which functions perform the operation. Spotting an object copy is generally easy, but identifying the copying function is not, as the following figure demonstrates.
Functions Copying Objects | Compiler-Created Copy Functions |
---|---|
void person::f1(person p3) // pass by value { ... } person person::f2() // return by value { ... return person(...); } person person::f3() // return by value { person temp(...); ... return temp; } |
person::person(person& p) // copy constructor { ... } person& person::operator=(person& p) // assignment operator { ... return *this; } |
Statements Triggering Object Copies | Comments |
person p1(p); |
Copy Constructor: Creates a new person object, p1, by copying an existing object, p. |
f1(p); |
Copy Constructor: The copy constructor creates the parameter object, p3 (see f1 above left), by copying the argument object, p. |
person p2 = p; |
Copy Constructor: The assignment operator, =, notwithstanding, the statement creates a new object, p2, and the copy constructor builds it by copying an existing object, p. |
p0 = p; |
Assignment Operator: The assignment operator copies an existing object to another existing object. Any data stored in p0 is overwritten. |
p0 = f2(); |
Assignment Operator: f2 returns an object by value, and the assignment operator copies it to an existing variable, p0. |
person p4 = f2(); |
No copy!: When a program defines a new object, e.g. p4, and creates a new object in the return statement (see f2), C++ builds the object in function-call scope - directly in p4 for this example. |
person p5 = f3(); |
Copy Constructor: Unlike f2, f3 creates and returns a temporary variable, which the copy constructor copies to p5. f3 triggers two more function calls than f2: one constructor and one destructor. |
The compiler-created copy constructor is necessarily simple and general. Our first task is to understand what the copy constructor does and then explore how the compiler might implement it.
string name; int weight; double height; |
|
(a) | (b) |
person::person(person& p)
{
name = p.name;
weight = p.weight;
height = p.height;
} |
person::person(person& p)
{
memcpy(this, &p, sizeof(person));
} |
(c) | (d) |
void* memcpy(void* dest, const void* src, size_t n);
void*
denotes the address of typeless data (i.e., data of an unspecified type) and is the most generic kind of data in a C++ program (similar in many ways to an Object reference in a Java program). size_t
is a type alias.Caution
A fundamental object-oriented principle is that a class hides its implementation - the data it stores and how its functions manage it - from programmers. Consequently, we can't know how a specific C++ compiler implements its string class, but it likely points to an array allocated on the heap. The string class overloads the assignment operator to handle this implementation. But memcpy is a low-level operation that is "unaware" of pointers and is unable to duplicate heap data. See Figure 4(b) below.The previous discussion of object ownership in aggregation relationships alluded to situations where two or more objects in a program share another object. While programmers can establish the sharing while copying a "complex" object, we generally intend the copy operation to produce two distinct and independent objects. Independence implies that once the copy operations are complete, we can change either object without affecting the other. The compiler-created copy constructor produces independent objects when the original object is "simple," but producing independent "complex" objects requires programmers to override the compiler-created copy constructor.
string* name;
int weight;
double height; |
|
(a) | (b) |
Steps for overriding the copy constructor
person::person(person& p) { name = new string(*p.name); weight = p.weight; height = p.height; } |
|
(a) | |
person::person(person& p) { memcpy(this, &p, sizeof(person)); name = new string(*p.name); } |
|
(b) | (c) |
memcpy
first and then copy each pointer member individually. Note that the order of operations is important: memcpy
must be done first, followed by the individual pointer copies. (Can you figure out why?)If the original object has an embedded or composed part, the program must initialize it. Initialization is automatic if the object's class has a default constructor. Otherwise, the overridden copy function must explicitly call a general constructor. The Actor 3 example in the next section demonstrates this process.
Complete versions of both programs described above are available for download and study (formatted with tabs set at eight spaces):
View | Download | |
---|---|---|
No pointer member variables | person.cpp | person.cpp |
One pointer member variable | person_pointer.cpp | person_pointer.cpp |