11.10 COPY CONSTRUCTORS AND COPY ASSIGNMENT OPERATORS IN C


11.10 COPY CONSTRUCTORS AND COPY ASSIGNMENT OPERATORS IN C++

Let's say we have a class without any pointer members:

     class X {         int n;     public:         X(.....) { ......... }     }; 

For a simple class such as this, the initializations and the assignment in the following statements have obvious meanings:

     void f() {         X x1;         X x2 = x1;                                               //(A)         X x3;         x3 = x2;                                                 //(B)     } 

The initialization at (A) is to be interpreted as: Copy byte-by-byte the data members of object x1 into the memory locations reserved for the data members of object x2. The assignment at (B) says the same thing-to copy byte-by-byte the data members of object x2 into the memory locations reserved for the data members of Object x3.

At the end of the function, when the variables x1, x2, and x3 go out of scope, all three will be deleted, even though all three are copies of the same object. This is not a problem because all three will occupy different places in the memory.

Now consider the case when one of the members is a pointer:

     class X {       int* ptr;       int size;     public:       X(.....) { ......... }       ~X() { delete[] ptr; }     }; 

The data member ptr is presumably a pointer to an array of ints. The second data member, size, will help us keep track of how many elements there are in the array. We will again define a function as before

     void f() {         X x1;         X x2 = x1;                                               //(C)         X x3;         x3 = x2;                                                 //(D)     } 

Let's start by assuming that the initialization at (C) and the assignment at (D) have the same semantics as before-a memberwise copy of the object on the right into the memory locations for the members of the object on the left side of the ‘=' operator. A memberwise copy for the initialization at (C) would cause x2.ptr to be a copy of x1.ptr, both pointers pointing to the same chunk of memory. The effect is the same as in the following two declarations:

     int* ptr = new int[5];     int* p = ptr; 

The pointers p and ptr will point to the same location in the memory.

If we also assume a memberwise copy for the assignment operator at (D), then x3.ptr will also point to the same chunk of memory as x2.ptr, which is pointing to the same memory as x1.ptr.

Therefore, just before the objects x1, x2 and x3 go out of scope as the thread of execution hits the right brace of the function f shown above, they will each contain a pointer member pointing to the same chunk of memory. As they go out of scope, they will all be deleted via their destructors, which is the destructor for class X. But this will cause triple deletion of the same chunk of memory pointed to by the data member ptr in each of the three objects. What happens when a program tries to free up the same chunk of memory multiple times in this manner is undefined. It may cause the program to behave in an unpredictable manner or it might even cause the program to crash.

Such anomalies can be avoided by defining what it means to copy an object for the purpose of initialization and for the purpose of assignment. The definition of what it means to copy an object for initialization is called a copy constructor. And the definition of what it means to copy an object for the assignment operation is called a copy assignment operator, or just an assignment operator.

For type T, the prototype of the copy constructor is[8]

     T(const T&); 

and the prototype of the copy assignment operator is

     T& operator=(const T&); 

The following would be an acceptable definition for a copy constructor for the class X we talked about before:

     //copy constructor:     X( const X& xobj ) {                                       //(E)         size = xobj.size;         ptr = new int[ size ];                                 //(F)         for (int i=0; i<size; i++) {                           //(G)             ptr[i] = xobj.ptr[i];         }     } 

This copy constructor's charge is to construct an object of type X whose members are set according to the composition of the specific object xobj that's supplied as an argument to the constructor in line (E). In line (F), the ptr member of the object under construction points to the freshly acquired memory for the array data member of the object under construction. And, then, in the for loop in line (G), we copy into this new memory the elements of the array from the argument object xobj.

In a similar manner, we can write a definition of the copy assignment operator for class X:

     //copy assignment operator:     X& operator=( const X& xobj ) {                              //(H)         if ( this != &xobj ) {                                   //(I)             delete [] ptr;                                       //(J)             size = xobj.size;             ptr = new int[ size ];                               //(K)             for (int i=0; i<size; i++) ptr[i] = xobj.ptr[i];     //(L)         }         return *this;                                            //(M)     } 

To fully appreciate this definition, recall that it will be called in the following sort of a situation

     X xobj_1(...);     X xobj_2(...);     xobj_1 = xobj_2;             // need a copy assignment operator 

The compiler reads the last line as

     xobj_1.operator=(xobj_2); 

Therefore, the keyword this in line (I) refers in this example to object xobj_1. The members size and ptr in lines (J) and (K) also refer to the object xobj_1. To explain what is happening in lines (I) through (M): we first delete in line (J) the block of memory pointed to by the ptr member of the object on the left side of the assignment operator, xobj_1. In line (K), the same member is then made to point to freshly acquired memory. In line (L), we then copy from the array that's a part of the object on the right side of the operator to the object on the left side. The boolean expression in line (I)

     if (this != &xobj) .... 

is meant to take care of self-assignment, as in

     X xobj(..);     xobj = xobj; 

In the event of such a self-assignment, the copy assignment operator does nothing, except returning a reference to the same object. And that brings us to line (M). The code shown above for the copy assignment operator would be perfectly legal even if it did not include line (M), with the proviso, of course, that we make the return type a void. What is being said here is that the operator function operator= does not need to return anything in order to do a correct job of the assignment. What we gain by including line (M) is that we can now chain together assignment operators like

     X xobj_1(...);     X xobj_2(...);     X xobj_3(...);     xobj_1 = xobj_2 = xobj_3; 

With the copy assignment returning a reference to the object constructed, this chaining is equivalent to

     xobj_1.operator=( xobj_2.operator=( xobj_3 ) ); 

While it is rare to see chaining of the assignment operator like "xobj_1 = xobj_2 = xobj_3;" in computer programs, one has to allow for it since that is dictated by the language definition of the assignment operator.

Note the important difference between the copy constructor in line (E) and the copy assignment operator in line (H): the latter includes memory deallocation in line (J).

Shown below is a redefinition of class X with the copy constructor and the copy assignment operator included:

 
//CopyAssignX.cc #include <iostream> using namespace std; class X { int* data; int size; public: //constructor: X( int* ptr, int sz ) : size(sz) { data = new int[size]; for ( int i = 0; i < size; i++ ) data[i] = ptr[i]; } //copy constructor: X( const X& xobj ) { size = xobj.size; data = new int[ size ]; for ( int i=0; i<size; i++ ) data[i] = xobj.data[i]; } //copy assignment operator: X& operator=(const X& xobj) { if ( this != &xobj ) { delete [] data; size = xobj.size; data = new int[ size ]; for ( int i=0; i<size; i++ ) data[i] = xobj.data[i]; } return *this; } //destructor: ~X() { delete [] data; } }; int main() { int freshData[5] = {1, 2, 3, 4, 5}; X x1(freshData, 5); X x2 = x1; X x3(freshData, 5); x3 = x2; return 0; }

The examples we have shown so far regarding when a copy constructor is invoked have all been of the following kind:

     class X {/*.... */ };        // class X has copy constructor     X xobj_1(/* ....*/);         // construct X object xobj_1     X xobj_2 = xobj_1;           // copy construct xobj_2        (N) 

A copy constructor, if available, would also be invoked for the following kinds of situations:

     class X { /* ... */ };     X xobj( /* ... */ };     X* ptr = new X( xobj );      // copy constructor invoked     (O) 

Note that the invocation X(xobj) on the right in line (O) matches the header X(const X&) of the copy constructor of class X. Comparing the statements in lines (N) and (O) above, the initialization syntax in (N) causes fresh memory to be acquired on the stack for the construction of a new X object. This memory is subsequently filled by copying over the contents of the object xobj_1 according to the copy constructor for X. On the other hand, in line (O) the operator new allocates fresh memory from the heap for constructing a new object of type X. The call X(xobj) then invokes the copy constructor of X for filling up this memory.

[8]The modifier const for the parameter in the headers of both the copy constructor and the assignment operator is not mandatory, but it makes for safer code and it permits both const and non-const objects to be duplicated. Making a duplicate without altering the original is not always desirable. For example, it is sometimes necessary to embed in a class the notion of ownership of an object that must not have more than one owner. Both the copy construction and assignment operations for such classes require transfer of ownership from the right-hand side to the left-hand side of the assignment operator. A smart pointer class, such as the auto_ptr class from the C++ Standard Library, represents this case. The prototype of a copy constructor for such a class would be

     T(T&); 

and the prototype of the assignment operator

     T& operator=(T&); 

Smart pointer classes are discussed in Chapter 12.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net