12 DATA ACQUISITION WITH OPERATOR OVERLOADING 373
There is a lesson to be learned from this exercise - whenever class objects are passed to a function it is prudent to pass them by reference. As shown in Listing 12-2, the keyword const can be prefixed to the parameter passed by reference to protect the parameter from any inadvertent changes within the function.
12.2.4 The Copy Constructor
In previous chapters we used default constructors and standard constructors to instantiate objects. The copy constructor is a special constructor that is called when a copy of an object is created. If the developer does not provide a copy constructor, the compiler will generate one by default. This constructor makes a copy of an object by copying member-by-member from one object to the other for three situations:
(i)When passing parameters to a function by value, a copy of the object must be created.
(ii)When parameters are returned by value, a copy of the object to be returned must be made. Again, the same copy constructor will be called to make the copy.
(iii)If an object is declared and initialised using another object passed as a parameter, the copy constructor must be called.
The assignment operator can also be used to initialise an object that was created previously using the default constructor. In principle, the assignment operator (=) must carry out the same actions as the copy constructor. If the developer does not overload the assignment operator the compiler will do so to suit the class. We will defer discussing overloading the assignment operator until operator overloading concepts have been described.
A few examples of object instantiation using various constructors are:
DCMotor Motor1; |
// default |
constructor used |
DCMotor Motor2(Motor1); |
// copy constructor used |
DCMotor Motor3; |
// |
default |
constructor |
used |
Motor3 = Motor1; |
// |
assignment operator |
used |
We will develop an example program that operates with arrays using the IntArray object to improve your understanding of the copy constructor. Consider the definition of the IntArray class given in Listing 12-3.
Listing 12-3 Header file intarray.h shows a class definition for an array of integers.
#ifndef IntarrayH #define IntarrayH
class IntArray
{
private:
374 12 DATA ACQUISITION WITH OPERATOR OVERLOADING
int NumInts;
int* ArrayPointer;
public: |
// Default constructor |
IntArray(); |
IntArray(int numints); |
// Constructor |
~IntArray(); |
// Destructor |
void EnterArray(); |
// Other member functions |
void PrintArray(); |
|
}; |
|
#endif |
|
The IntArray class will instantiate an array of integers having a specified number of elements. The data member NumInts will store the total number of elements in the array and the pointer ArrayPointer will point to the dynamically allocated portion of memory containing the array of integers. The destructor ~IntArray() will release the dynamically allocated memory. The function EnterArray() will prompt the user for array values, receive user input via the keyboard, and fill the array. The final function PrintArray() is used to print the contents of the array on the screen.
The IntArray class’s constructor and its default constructor initialise the member data NumInts and ArrayPointer. If a parameter is passed to the constructor, memory for the array will be dynamically allocated as shown in the following constructor definitions:
IntArray::IntArray() // Default constructor
{
NumInts = 0; ArrayPointer = NULL;
} |
|
IntArray::IntArray(int numints) |
// Constructor |
{ |
|
if(numints <=0) |
|
{ |
|
NumInts = 0; |
|
ArrayPointer = NULL; |
|
} |
|
else |
|
{ |
|
NumInts = numints;
ArrayPointer = new int[NumInts];
}
}
12 DATA ACQUISITION WITH OPERATOR OVERLOADING 375
Note that if the value of numints is 0 or negative, NumInts is initialised to 0 and ArrayPointer is initialised to the predefined constant NULL to indicate that the pointer is not pointing anywhere.
Suppose we use the object class IntArray in a main() function to instantiate two IntArray objects named A and B. We will then pass the objects A and B by value to a function named AddArrays() to add the IntArray object A to the IntArray object B as shown in the fragment of code below:
void AddArrays(IntArray a, IntArray b) // Pass by value
{
// print the result of summation on-screen
}
void main()
{
IntArray A(5); IntArray B(5);
AddArrays(A,B);
.
.
.
}
Object A |
|
Object B |
NumInts |
|
NumInts |
ArrayPointer |
|
|
|
|
|
|
|
ArrayPointer |
|
|
|
|
|
Copy of A |
|
|
|
|
|
Copy of B |
|
|
|
|
|
|
|
|
|
NumInts |
|
|
|
|
|
NumInts |
|
|
|
|
|
ArrayPointer |
|
|
|
|
|
ArrayPointer |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Memory dynamically allocated by the constructors
Figure 12-1 Objects A and B copied by the compiler-generated copy constructor.
When the actual arguments A and B are passed by value to the function AddArrays(), copies of A and B must be made. The compiler-generated copy
376 12 DATA ACQUISITION WITH OPERATOR OVERLOADING
constructor will be called to make these copies; this copy-making process is shown graphically in Figure 12-1.
The compiler-generated copy constructor has created a copy of each data member of the original objects. However, it did not make copies of the dynamically allocated memory. As a result, the pointer to the original object and the pointer to the copied object point to the same portion of memory.
The destructor ~IntArray() is called after the function AddArrays() executes. The destructor will free the memory used for the temporary copies of A and B together with the original data objects that were dynamically allocated. This outcome is shown in Figure 12-2.
Object A |
|
|
Object B |
|
|
NumInts |
|
|
NumInts |
|
|
ArrayPointer |
|
? |
ArrayPointer |
|
? |
|
|
Figure 12-2 Result of discarding the copies of A and B.
|
|
|
|
|
|
|
|
|
|
|
|
Object A |
|
Object B |
|
NumInts |
|
|
|
|
|
NumInts |
|
|
|
|
|
|
|
ArrayPointer |
|
|
|
|
|
|
ArrayPointer |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Memory dynamically allocated |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copy of A |
|
|
by the constructors |
|
|
|
|
|
|
Copy of B |
|
|
|
|
NumInts |
|
|
|
|
|
NumInts |
|
|
|
|
|
|
ArrayPointer |
|
|
|
|
|
|
ArrayPointer |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Memory dynamically allocated |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
by the copy constructors |
|
|
|
|
|
|
|
|
|
|
Figure 12-3 Copies of objects A and B made by a user written copy constructor.
Now the main() function has lost the original data it had created at the time of instantiating the objects A and B. We can overcome the problem created by the compiler-generated copy constructor if we write our own copy constructor that not