Inheritance- II


The following topics are covered in this section:


Overloading and Overriding

There are a few terms that are repeatedly used in C++ programming; you could call them C++ programming jargon and you’ll find references to these words often. Overloading and overriding belong to that category and it is important to know the difference between these two terms. We have already discussed about function overloading. In overloading, though the function names are the same, the functions will have different types of parameters. Thus depending on the types of arguments, the compiler will correctly choose between the functions when they have the same name.

So, what is overriding? Overriding occurs when the functions have the same name and same return type. You might wonder as to how the compiler will distinguish between functions that have the same name and arguments. Actually in this case the compiler will not be able to decide which function to use while compiling. The choice is made at run-time. Overriding is related to inheritance and is implemented using virtual functions.

The problem

Let’s take a simple problem. We want to write a program that will ask the user as to what they want to create: a general car or a ferrari. Depending on the user’s input we want to display some details of the car. (The example might seem a bit trivial but it should give you a good idea about the problem we are going to face).

We’ll make use of a base class pointer in this program. The topic on pointers and inheritance is discussed later but for the time being just remember that a base class pointer can point to an object of the base class as well an object of the derived class.

#include <iostream.h>
class car
{protected:
int speed;
char color[20];

public:
void display( )
{
cout<<"\nThis is a general Car";
}
};

class ferrari : public car
{public:
void display( )
{
cout<<"\nThis is a Ferrari";
}
};

int main( )
{char choice;
car *ptr;
cout<<endl<<"Do you want a ferrari(f)/general car(g)? ";
cin>>choice;
if (choice=='g')
{
ptr=new car;
}
else if (choice=='f')
{
ptr=new ferrari;
}
else
{
cout<<endl<<"Invalid choice- terminating program";
return 1;
}

(*ptr).display( );
delete ptr;
return 0;
}

The output if you choose a ‘g’ is:

Do you want a ferrari(f)/general car(g)? g
This is a general Car

The output if you choose a ‘f’ is:

Do you want a ferrari(f)/general car(g)? f
This is a general Car

We have made use of the same function name ‘display’ in both the classes because both perform the same function (the difference is that each one is supposed to display details pertaining to that object). The results are not what we wanted. When a ferrari object is created we wanted the display( ) function of the class ‘ferrari’ to be called (but instead in this program the base class definition has been called). The reason for this is that the compiler decides what has to be called at compile time itself (which actually it shouldn’t do because we will know the user’s input only at run-time). For doing this we need to explicitly tell the compiler to wait till run-time using the keyword ‘virtual’. When a function is declared to be virtual, the decision as to which function to use will be decided at run-time rather than at compile time.


Virtual Function (late/dynamic binding)

A virtual function is a member function of a base class, which is redefined by a derived class. You need to make use of the keyword ‘virtual’, to make a function virtual. The function is redefined in the derived class to meet the needs of the derived class.
The main advantage of virtual function is that they support run-time polymorphism.

#include <iostream.h>
class car
{protected:
int speed;
char color[20];
public:
virtual void display( )
{
cout<<"\nThis is a general Car";
}
};
class ferrari:public car
{public:
void display( )
{
cout<<"\nThis is a Ferrari";
}
};
class mclaren:public car
{public:
void display( )
{
cout<<"\nThis is a McLaren";
}
};
int main( )
{car *p;
car mine;
ferrari f;
mclaren m;
p=&mine;
p->display( );
p=&f;
p->display( );
p=&m;
p->display( );
return 0;
}
The output would be:

This is a general Car
This is a Ferrari
This is a McLaren

The function display ( ) is made virtual. Notice that the ‘virtual’ keyword is used only in the base class’ display ( ) function. The derived classes ‘ferrari’ and ‘mclaren’, redefine the display ( ) function.

car *p;

We are creating a pointer to point to ‘car’ (which is a class). Hence you can say that ‘p’ is pointing to an object of type ‘car’. A pointer which points to an object of the base class, can point to an object of a derived class. But the reverse is not possible.
The next statement is:

p = &mine;

This assigns the address of the object ‘mine’ to the pointer ‘p’. Remember that ‘p’ is a pointer to ‘car’ type and ‘mine’ is an object of type ‘car’. So there's no problem in this assignment.

Pointers can be used to invoke member functions. Invoking in this way is done by using the following operator: -> (it is a minus followed by the less than sign. It is called the arrow operator). Since ‘p’ is pointing to an object of type ‘car’, the display( ) function of the base class will be executed. If ‘p’ were an object of type ‘car’, we would call the member function using the dot operator:

p.display( );

But since it is a pointer, you have to call it using the arrow operator.
The next statement is:

p = &f;

A pointer to object of the parent class can point to an object of a derived class. ‘f’ is an object of the class ‘ferrari’ (which is derived from ‘car’). Therefore, p can point to ‘f’ and the address of ‘f’ is now assigned to p. When the display ( ) function is now invoked through the pointer ‘p’, the compiler will run the derived class’ version of the display ( ) function.

This decision of choosing which function to execute is made at run-time. Hence this is known as run-time polymorphism. In effect you are actually overriding the existing base class display ( ) function. This not overloading because the name of the function, the return data-type and the parameters of the function in the parent as well as the derived class are the same. Another term used is ‘late binding’. In this situation ‘binding’ refers to the decision that has to be made regarding which function should be called (this depends on the type of object pointed to by the pointer and it can be determined only at run-time). This method of using virtual functions helps achieve ‘late binding’ (i.e. the function call is not resolved till the program is run). The opposite of late binding is ‘early binding’ and in this case everything about the function call is known at compile-time itself. All the member functions called through the normal use of the dot operator are examples of early binding. The advantage of early binding is that it will make the program faster (since everything is determined at compile-time itself). In ‘late binding’ the decision has to be made at run-time and hence execution of the program could be slower. But ‘late binding’ provides a lot of flexibility. ‘Late binding’ is also sometimes called ‘dynamic binding’ and ‘early binding’ is called ‘static binding’.

What will happen if we don’t redefine a virtual function in the derived class? If you don’t redefine a virtual function in the derived class, the derived class will execute the base class’ function.


Pure Virtual Functions and Abstract Classes

One very useful feature of virtual functions is creating pure virtual functions. When a virtual function is equated to zero, it becomes pure virtual. For example:

#include <iostream.h>
class car
{protected:
    int speed;
    char color[20];
public:
virtual void display( )=0;         //Pure virtual Function
};
class ferrari:public car
{public:
void display( )
{
cout<<"\nThis is a Ferrari";
}
};
class mclaren:public car
{public:
void display( )
{
cout<<"\nThis is a McLaren";
}
};
int main( )
{car *p;
ferrari f;
mclaren m;
p=&f;
p->display( );
p=&m;
p->display( );
return 0;
}

The output is:

This is a Ferrari
This is a McLaren

The advantages of pure virtual functions are:

There are cases where you might not want to create objects belonging to the base class and you might feel that a particular function would make no sense in the base class. In all these cases you can make use of pure virtual functions in the base class.

If you go back to the example of the class ‘bacteria’, it wouldn’t make sense to create a concrete object belonging to the class ‘bacteria’. You will want to create particular strains of bacteria but not general bacteria objects and in this case the bacteria class can be made abstract so that no one can ever create an object of type bacteria (the users of your class can thus only create objects belonging to classes derived from ‘bacteria’).


Implementing Polymorphism

Polymorphism has already been explained in the Chapter on classes. Polymorphism is simply giving different meanings to the same name (in C++ this can be a function or an operator). C++ supports compile time and run-time polymorphism. Polymorphism tends to be associated frequently only with virtual functions (late/dynamic binding). But C++ implements polymorphism in a number of ways. They are through the use of:

We have already seen three of these methods. Templates will be described in Chapter 13. It has to be noted that run-time polymorphism is implemented only through the use of virtual functions. Polymorphism that is implemented through mechanisms other than virtual functions (like operator and function overloading) is also called "ad hoc polymorphism".


Go back to the Contents Page 2


Copyright © 2004 Sethu Subramanian All rights reserved.