10.5.4. Overriding The Copy Constructor

Time: 00:04:07 | Download: Large, Large (CC), Small | Streaming (CC) | Slides (PDF)
Review

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:

Simple classes (without pointer members)
The compiler-created byte-wise copy constructor and assignment operator are sufficient.
Complex classes (with one or more pointer members)
Programmers must override the copy constructor and the assignment operator.

Operations That Copy Objects

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 ObjectsCompiler-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 CopiesComments
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.
Operations copying objects. The C++ compiler automatically creates two functions, a copy constructor and an assignment operator, to copy objects in a program. The figure illustrates three functions utilizing one of the copy functions and the statements calling them. However, sometimes, it's unclear which copy function a statement calls. The ellipses represent detail removed for simplicity. See Person.cpp at the bottom of the page.

The Compiler-Created Copy Constructor

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.

The Person UML class diagram (the diagram only shows attributes):

Person
------------------
-int : id
-weight : int
-height : double
int     id;
int	weight;
double	height;
Copying a simple object creates a new object identical to the original.
(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)
Copying a "simple" object with the compiler-created copy constructor1. The compiler automatically creates a basic copy constructor that correctly copies "simple" objects. Programs copy or duplicate objects by allocating their memory and calling a copy constructor to initialize their member variables. The compiler-created copy constructor copies the values from the exiting object's member variables to the new object's corresponding members.
  1. The Person class's member variables: UML class diagram and C++ code. Class developers don't include the compiler-created copy constructor in the UML class diagram or any C++ code.
  2. A symbolic representation of an object copy - the constructor copies the values saved in one object to a new object.
  3. This programmer-overloaded copy constructor mirrors the behavior of the compiler-crated constructor, which copies each member variable.
  4. Copying a block of memory byte-by-byte is easy and efficient - some systems implement it with a single machine-instruction. This approach does not require the compiler to "know" about individual members - only the new object's address and size. The memcpy function prototype is:
    void* memcpy(void* dest, const void* src, size_t n);
    • dest is the address receiving the data
    • src is the address of the original data
    • n is the number of bytes to copy
    • 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.

Overriding The Copy Constructor

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;
Copying a complex object duplicates the whole object but not the aggregated parts. The new and original whole objects share (point to) the same aggregated parts.
(a)(b)
Copying a "complex" object with the compiler-created copy constructor. For this example, imagine that we want to replace a person's ID with a more familiar "name" attribute, and, to further the demonstration, we implement it with a pointer to a string. The picture (b) illustrates what happens when a program copies a "complex" object with the compiler-created copy constructor.
  1. This version changes the id member variable to a string pointer, changing the Person class from a "simple" to a "complex" class.
  2. The compiler-created copy constructor accurately copies the original object's member variables, including its pointers. But the value saved in a pointer is an address. Consequently, the original and the copied objects save the same address in their respective pointer members - they point to the same part object - and are not independent. Changing the name in either object also changes the name in the other.
The compiler-created assignment operator behaves similarly, as seen in the next chapter.

Steps for overriding the copy constructor

  1. The function's name, like all constructors, is the name of the class.
  2. The function has exactly one parameter, an instance of the defining class, passed by reference.
  3. Copy each non-pointer member variable by simple assignment or by using memcpy.
  4. Copy each pointer member by allocating new memory with the new operator and copying the saved data from the original to the new object.
Person::Person(Person& p)
{
    name = new string(*p.name);
    weight = p.weight;
    height = p.height;
}
A correct overridden copy constructor will copy the original whole object and all aggregated parts.
(a)
Person::Person(Person& p)
{
    memcpy(this, &p, sizeof(Person));
    name = new string(*p.name);
}
(b)(c)
Overriding the copy constructor.
  1. This version copies the original object member-by-member, a common approach for overriding a copy constructor. It's common to dereference any pointers before duplicating the pointer members, but this depends on the other constructors in the class.
  2. When there are many non-pointer members, it is more efficient to do a byte-wise copy with 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?)
  3. When the copy operation is complete, the pointer members are also correctly copied, resulting in distinct and independent objects that do not share data.
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.

Downloadable Code Examples

Complete versions of both programs described above are available for download and study (formatted with tabs set at eight spaces):

 ViewDownload
No pointer member variables person.cpp person.cpp
One pointer member variable person_pointer.cpp person_pointer.cpp

  1. An earlier version of this section specified a string as the first member, as illustrated here. The member-by-member copy always works because the string class overloads the assignment operator (highlighted). (The next chapter describes how programmers overload operators.) However, the memcpy version was problematic, working with some compilers but not others. C++ compilers can choose between various valid string class implementations. The success or failure of memcpy depends on how the compiler implements the string class, making the memcpy version of the copy constructor unreliable and non-portable.
    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));
    }