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.
int id; int weight; double height; |
|
(a) | (b) |
Person::Person(Person& p)
{
id = p.id;
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.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, they generally intend the copy operation to produce two distinct and independent objects. Independence implies that once the copy operations are complete, programs can change either object without affecting the other. When the original object is "simple" (i.e., it doesn't have pointer members), the compiler-created copy constructor creates independent objects. However, programmers must override the compiler-created copy constructor to achieve independence when copying "complex" objects (with one or more pointer members).
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 significant: the function must run memcpy
first, followed by the individual pointer copies. (Can you explain why?)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 |
Class Specification | Member Copy | Unreliable Byte-Copy |
---|---|---|
string name; int weight; double height; |
Person::Person(Person& p) { name = p.name; weight = p.weight; height = p.height; } |
person::person(person& p) { memcpy(this, &p, sizeof(person)); } |