Copy constructors, pointers to objects


The following topics are covered in this section:


Pointers to Objects

You can create pointers to objects and to access the member functions you will have to use the arrow operator just like you would do for structures.

class bacteria
{

private:
    int life;
public:
bacteria( )
{
    life=1;
}
void display( )
{
    cout<<"Life is : "<<life;
}

};
int main( )
{
bacteria *p;
bacteria ecoli;
p=&ecoli;
p->display( );
return 0;
}

The output is:

Life is : 1

You cannot attempt to access any private members of a class using a pointer to an object (data hiding is retained irrespective of whether you create an object or a pointer to an object).


How are objects stored in memory?

We know that classes are just a framework- they do not occupy memory space. Instances of the class (objects) will occupy memory space. But a class has member data as well as member functions. It is obvious that every instance of the class should have its own copy of the member data; but is it necessary that every object should have a copy of all the member functions? The member data values will vary from object to object but the member functions are going to be the same.

Member functions are implemented by taking advantage of this fact and hence only a single copy of the member functions is stored in memory. For example:

#include <iostream.h>

class heater

{
private:
    int temp;

public:
heater( )
{
temp=0;
}

void set_temp(int x)
{
temp=x;
cout<<"temperature is now:"<<temp;
}

//…..other functions

};

int main( )
{
heater h1,h2,h3;
h1.set_temp(40);
h2.set_temp(50);
h3.set_temp(60);
return 0;
}

In the above program, each of the 3 objects will have their own ‘temp’ member but the function set_temp( ) is common to all 3 of them.

The diagram below illustrates this fact.

Now an interesting question arises. If all objects are going to access the same member functions how does the function operate on the correct object? How does the function know on which object it has to act upon? How is it that when h1 calls set_temp(), the member ‘temp’ of h1 gets set?

This indicates that there must be a way in which the member function can identify who called it? The member function set_temp( ) should be able to differentiate between the objects h1, h2 and h3. To achieve this we pass a special member to the function (which the function uses to identify who called it). This member is the ‘this’ pointer.


‘this’ Pointer

We’ve seen that objects can call member functions (public functions) by simply using the dot operator. But the member functions are not called as we might think. The address of the object (i.e. the object calling the function) is passed to the member function. This is passed as an argument to the member function. You might be thinking, "We never mentioned any argument in my member function. Where does this argument go?"

Actually this is a kind of hidden argument (something that isn’t explicitly stated). Where does this argument go? The hidden argument is the address of the object and hence it has to go to a pointer. What is the name of the pointer? This pointer is called as ‘this’ pointer because it will point to the address of the object which called the member function (it will point to this object or in other words the present object). In an earlier example (the one with a class called ‘car’), we came across the function:

void input( )
{
cout<<"\nEnter the color : ";
cin>>color;
cout<<"\nEnter the speed : ";
cin>>speed;
}

This is a member function belonging to the class called ‘car’. ‘color’ and ‘speed’ are member data of that class. In reality, the above function is equivalent to:

void input( )
{
cout<<"\nEnter the color : ";
cin>>this->color;
cout<<"\nEnter the speed : ";
cin>>this->speed;
}

In fact the compiler will convert our code into something similar (since it has to use the ‘this’ pointer to access the member data of the correct object). The ‘this’ pointer is useful when a member function of a class has to pass the address of the current object to another part of the program (or another function).

Beware: The ‘this’ pointer is not present in static member functions. It is also not present in friend functions (because friend functions are not members of a class).


Copy Constructors

It’s time to go back to the problem we discussed earlier (the mystery of the default copy constructor). A copy constructor is invoked when you initialize a new object of a class using an existing object. This will happen when: 

·        You pass a copy of an object as argument to a function (i.e. when passing by value).

·        When you return an object from a function

·        Or initialize an object during declaration using another object. 

If we don’t specify a copy constructor, the compiler already has a default copy constructor. This default copy constructor will simply perform a bit-by-bit copy of the original object to the new object (i.e. it will blindly copy everything to the new object). This can lead to problems (especially if you use the ‘new’ and ‘delete’ operators in the constructor and destructor of the class). An example was discussed earlier in this chapter (using the ‘rect’ class).

We’ll start by considering the same example: 

class rect
{
private:
            int length, breadth;

public:
            rect( )
            {
                        cout<<endl<<"This is the constructor with no arguments";
                        length=breadth=0;
            }

            rect(int x, int y);

            void area( )
           {
                        cout<<endl<<"The area of the rectangle is : "<<length*breadth;
            } 

            ~rect( )
           {
                        cout<<endl<<"Rectangle destroyed";
            }
}; 

rect::rect(int x, int y)
           {
                        cout<<endl<<"New rectangle created";
                        length=x;
                        breadth=y;
            } 

int main( )
{
            rect ob1(10,9);
            rect ob2=ob1;
            rect ob3(ob1);
            rect ob4;
            ob4=ob3;
            ob1.area( );
            ob2.area( );
            ob3.area( );
            ob4.area( );
            return 0;

The output is: 

New rectangle created
This is the constructor with no arguments
The area of the rectangle is : 90
The area of the rectangle is : 90
The area of the rectangle is : 90
The area of the rectangle is : 90
Rectangle destroyed
Rectangle destroyed
Rectangle destroyed
Rectangle destroyed

 Let’s try to map the output with the code we wrote: 

rect ob1(10,9);
rect ob2=ob1;
rect ob3(ob1);
rect ob4;
ob4=ob3; 

We’ve created 4 objects and we have 4 destructors but only 2 constructors. Why?

ob1 is created using the parameterized constructor. No problems here since we are calling the constructor directly.

ob2 and ob3 do not call the constructor. They are initialized using ob1 and in these 2 cases it is the default copy constructor that is called.

ob4 is created using the no argument constructor. Thus we’ve called only 2 constructors and the problem is because of our default copy constructor. What we need to do is to provide our own copy constructor. A copy constructor for the above program would be defined as shown below: 

            rect(const rect &rc)
            {
                        cout<<endl<<"invoked copy constructor.";
                        length=rc.length;
                        breadth=rc.breadth;
            } 

This is relatively simple. It is just another constructor which takes an argument of an object (we pass the object to this constructor by reference and not by value – if we passed it by value we’ll be creating another temporary object). Add the above code to the program and run it again.

The output will now be: 

New rectangle created
invoked copy constructor.
invoked copy constructor.
This is the constructor with no arguments
The area of the rectangle is : 90
The area of the rectangle is : 90
The area of the rectangle is : 90
The area of the rectangle is : 90
Rectangle destroyed
Rectangle destroyed
Rectangle destroyed
Rectangle destroyed 

As you can see, the copy constructor is called twice (for ob2 and ob3). That’s good but you might ask, “Why should I worry about this copy constructor?”. We’ll take another example. 

Let us suppose that we are using the ‘new’ operator in the constructor of the class and ‘delete’ is used in the destructor of the class. We also have a function whose argument is an object of that class. While calling this function, we will pass an existing object to the function. The function will create a temporary object (a copy of the object) but it will not invoke the constructor for this new object. Hence the ‘new’ operator (present in the constructor) is not invoked and an exact replica of the passed object is made. During destruction, the temporary object will invoke the destructor (which has the ‘delete’ operator and it will delete the portion of memory allocated to the original object). When the original object is destroyed, it will also invoke the destructor and this will again try to free the same memory. This can lead to serious problems. To solve this problem we have to define our own copy constructor.  

Let ‘bacteria’ and ‘virus’ be two classes. Each one will have a variable called ‘life’ created dynamically using the ‘new’ operator. We shall also define a friend function to which we will pass objects of both ‘bacteria’ and ‘virus’.

#include <iostream.h>

class virus;
class bacteria
{
private:
            int *life;
public:
            bacteria( )
            {
                        life=new int;
                        cout<<"\nCreated Bacteria";
                        *life=1;
            }

            ~bacteria( )
            {
                        cout<<"\ndestroyed bacteria.";
                        delete life;
            }
            friend void check(bacteria, virus);
};

class virus
{
private:
            int *life;

public:
            virus( )
            {
                        life=new int;
                        cout<<"\nCreated virus";
                        *life=0;
            }

            friend void check(bacteria, virus);

            ~virus( )
            {
                        delete life;
                        cout<<"\ndestroyed virus";
            }
};

 void check (bacteria b, virus v)
{
            if (   (b.life[0]==1) || (v.life[0]==1)   )
                        {
                                    cout<<"\nSomething is alive";
                        }

int main( )
            {
            bacteria fever;
            virus cholera;
            check(fever,cholera);
            return 0;
            }
 

The output will be: 

Created Bacteria
Created virus
Something is alive
destroyed bact.
destroyed virus 

And then an error is generated when using VC++ (because the same memory area is being deleted twice)…. 

In Visual C++ compiler it generates an error during run time and other compilers will also produce something similar (or in the worst case your program could freeze). Anyway, this can lead to drastic effects so you have to program in such a way that this problem does not occur. We’ll add our own copy constructors to both the classes.

Add the following to the bacteria class:

             bacteria(const bacteria &ba)
            {
                        cout<<endl<<"invoked bacteria copy constructor.";
                        life = new int;               
                        life[0]=ba.life[0];
            } 

and the following to the virus class: 

            virus(const virus &vi)
            {
                        cout<<endl<<"invoked virus copy constructor.";
                        life = new int;
                        life[0] = vi.life[0];                     
            } 

Run the program and the output will now be: 

Created Bacteria
Created virus
invoked virus copy constructor.
invoked bacteria copy constructor.
Something is alive
destroyed bacteria.
destroyed virus
destroyed virus
destroyed bacteria. 

There is only one part to explain in the above program, the copy constructor. Let’s take a look at the copy constructor for the ‘bacteria’ class. 

bacteria(const bacteria &ba)
            {
                        cout<<endl<<"invoked bacteria copy constructor.";
                        life = new int[2];                      
                        life[0]=ba.life[0];
                        life[1]=ba.life[1];
            } 

So, what do we do here? The basic idea is to copy all the member data values from the original object into the new object that is being created. But we don’t want to simply copy bit-by-bit. We are making use of the ‘new’ operator in the constructor of the class and so, in the copy constructor we make use of the ‘new’ operator again. In effect, this will make the program allot a new memory area to ‘life’ of the new object. ‘life’ of the original object and ‘life’ of the new object created will not be in the same area. But we want the value of the new life to be the same as the original one. So we code:

                        life[0]=ba.life[0];
                        life[1]=ba.life[1];

This ensures that the values will be the same.

Now when the temporary object is destroyed, the destructor will be called and memory will be freed. When the original object is destroyed, the destructor is called again and memory (a different area) is freed up. Hence there is no clash of memory.  

Another question is why do we use const in:

            bacteria(const bacteria &ba)

A copy constructor is used only for the purpose of copying; to initialize a new object based on an existing one. In this case you don’t want the copy constructor to modify the existing object (so we use ‘const’). 

Note: The above example was purely meant for explaining the idea of copy constructors. You wouldn’t really be creating a variable like ‘life’ dynamically.  

Another solution: 

Instead of using the copy constructor we could have taken a more simpler approach as well. The problem arose because we were passing 2 objects to the friend function by value. If we passed them by reference then the compiler would never need to create temporary objects. What should we do to achieve this? First declare the friend function as: 

            friend void check(bacteria &, virus &);

and then define it as: 

void check (bacteria &b, virus &v)
{
            if (   (b.life[0]==1) || (v.life[0]==1)   )
                        {
                                    cout<<"\nSomething is alive";
                        }

Try the program and you’ll get what we actually wanted: 

Created Bacteria
Created virus
Something is alive
destroyed virus
destroyed bact. 

Beware: The copy constructor will not work when you use the assignment operator (=) to assign one object to another. In such cases, you have to overload the = operator to avoid the problem (operator overloading is discussed in the next chapter). 

If our above program had: 

int main( )
{
            bacteria fever;
            bacteria ecoli;
            ecoli=fever;                  //RUN-TIME ERROR
            return 0;

we’ll again get into memory problems because now we are using the assignment operator to copy ‘fever’ to ‘ecoli’. This leads to the same problem we had with copy constructors (both fever and ecoli will now access the same memory location for ‘life’ and both will try to free the same memory location). This just proves the point that when you perform such assignments, the copy constructor does not come into play. 

Remember:

            bacteria ecoli=fever;

is different from

bacteria ecoli;
            ecoli=fever; 

The first case is an initialization using an exiting object (copy constructor comes into action) but in the second case we are performing an assignment (assignment operator comes into action).


Go back to the Contents Page


Copyright © 2004 Sethu Subramanian All rights reserved. Sign My Guestbook