Polymorphism and C++ with Virtual Functions
Introduction
Here, it is explained about how the compiler implements polymorphism and about knowledge of Assembly Language to make it easy to understand Polymorphism in a better way. Here, the explanation is simplified to grasp them well. If you are not that acquainted with Assembly, doesn't matter you can grasp it well.
Polymorphism
It is the capability to call different functions by just using one type of function call. It is useful since it can group classes and their functions together. Polymorphism is the most important part of Object-Oriented Programming. Some people feel that if they have an idea of what classes are they have stepped in the object-oriented world. But this is not true. Polymorphism is the core of object-oriented programming and if anybody stops here he's missing out the best part of Object Oriented Programming (OOP). And it is easy to grasp well.
Eg: If you want to make a picture with circles, squares, lines and triangles we can make a class Shape and create an instance of it like this:
Shape *s[100];
Here all the addresses of the objects of the other classes are stored in the Shape Array. And then to draw the Picture all we have to do is:
for(int i=0;i<=100;i++)
s[i]->draw();
When the loop runs different draw functions of each class is called because:
The implementation of Functions from different classes are through the same function call.
The Array s[] has been defined to contain shape pointers and not square or triangle pointers.
And it is actualized by Virtual Functions. Now let us see what virtual functions are.
The virtual functions
Virtual means to appear like something while in reality it is something else. For example, when virtual functions are used, a program appears to call a function of one class but actually it may be calling a function from another class. In the previous example draw () is a virtual function since it calls different draw functions from different classes by using the same function call draw ();
Which draw () function would get used depends on the contents of s[i].But for this polymorphic approach to work we must satisfy the following forms:
The Base class must contain a draw() function which is declared virtual.
All other classes (line, circle, etc.) should be derived from the base class.
This may be hard to understand in just one go so we'll start using programs that'll help us understand better. See this procedure:
#include
class base //Base Class
{
public:
void func()
{
cout<<"In base::func()\n";
}
};
class d1:public base // Resulting Class 1
{
public:
void func()
{
cout<<"In d1::func()\n";
}
};
class d2:public base // Resulting Class 2
{
public:
void func()
{
cout<<"In d2::func()\n";
}
};
void main()
{
d1 d;
base *b=&d;
b->func();
d2 e;
b=&e;
b->func();
}
Run this program and you would see that the output would be:
In base::func()
In base::func()
Since the compiler allows a pointer of a base class to accept addresses of derived class objects. This is known as UPCASTING. Here the Compiler looks at the type of pointer b and since it belongs to the base class it calls the base class function.
But now, let's make a slight modification in our program. Precede the declaration of func() in the base class with the keyword virtual so that it looks like below:
virtual void func()
{
cout<<"In base::func()\n";
}
Compile and Run the Program. Now the Output is:
In d1::func ()
In d2::func ()
Here the Compiler looks at the contents of the pointer instead of it's type. Hence since addresses of objects of d1 and d2 classes are stored in *b the respective func() is called. But this way how does the compiler know which function to compile when it doesn't know which object's address 'b' might contain and Which version does the compiler call.
Even the compiler does not know which function to call at compile-time. Hence it decides which function to call at run-time with the help of a table called VTABLE. Using this table the compiler finds what object is pointed by the pointer b and then calls the appropriate function.
It is a method by which the compiler decides which function to call at run-time is known as late-binding or dynamic-binding. It slows down the program but makes it a lot more supple.
Clean virtual functions
We realise that since the base class virtual function never gets called anyway we'd better keep it's body blank. But there's a better way to do this. We can change the virtual function func() in the base class to the next:
virtual void func()=0;
The =0 is not an assignment operator here but it is just a way of telling the compiler that the function has no body. But there is another side of this. An object of a class which contains a pure virtual function cannot be created. It seems logical enough ie. If you have classes triangle, square, circle derived from shape class we wouldn't want to make an object of the shape class. Hence the shape class should be provided with a pure virtual function. If you even try to create an object of a class containing a pure virtual function the compiler would report an error even pointing out which pure virtual function prevents you from making a point.
The procedures of virtual functions
Application of Virtual Functions is just one part of polymorphism and knowing how they work completes the other half. When the keyword 'virtual' is inserted in the declaration of the function the compiler inserts all mechanisms in the program to use Virtual Functions. Each Class has a VTABLE that stores the functions that it can access and each class contains a VPTR that can access the VTABLE. Look at this program and the table below it and you will understand the VTABLE and the VPTR. It is explained in a simple way.
#include
class item
{
public:
virtual void price()
{
printf("In item::price()\n");
}
virtual void type()
{
printf("In item::type()\n");
}
void display();
};
void item::display(){printf("In item::display()\n");}
class microwave:public item
{
public:
void price()
{
printf("Microwave::Price()\n");
}
void type()
{
printf("Microwave::type()\n");
}
};
class computer:public item
{
public:
void price()
{
printf("Compuer::Price()\n");
}
};
class radio:public item
{
public:
void type()
{
printf("radio::type()\n");
}
};
void main()
{
microwave m1;
computer c1;
radio r1;
item *i=&m1;
i->price();
i->type();
printf("\n");
i=&c1;
i->price();
i->type();
i->display();
printf("\n");
i=&r1;
i->price();
i->type();
printf("\n");
microwave m2;
i=&m2;
i->price();
i->type();
i->display();
printf("\n");
}
The Output of this Program would be:
Microwave::Price()
Microwave::type()
Compuer::Price()
In item::type()
In item::display()
In item::price()
radio::type()
Microwave::Price()
Microwave::type()
In item::display()
Is there non-compulsory theme for virtual functions?
Here the Normal Function calls are called by the Assembly instruction 'call' while virtual functions require complex instructions. This takes up code space as well as execution point.
The speed of the code is reduced in Virtual Functions. Some languages like SmallTalk perform Late Binding every time a function is called and hence SmallTalk Programs aren't fast enough. But C++ is a Superset of C where Efficiency is important and hence C++ allows both static binding as well as late binding. The default convention used is static binding so that there is no loss in pace.
Since the Virtual Functions in your classes reduce execution speed we can use it all the time. In fact, it makes it easier to manage and code and its advantages are more than its disadvantages. It is better to use Virtual Functions in your classes wherever possible.
Base classes virtual functions
If there is an occasion when a 'base' class has two classes derived from it, what is to be done. Take an eg. For it. Resulting1 and resulting 2. Suppose we create another class which derives itself from both the derived classes ie. derived3. Now suppose a member function of derived3 wants to access data or functions in a base class. Since derived1 and derived2 are derived from base each inherits a copy of base. This copy is referred to as a sub object. Now when derived3 refers to the data in the base class, which of the two copies should it access? The compiler notices this ambiguous situation and reports an error. To get rid of this we should make derived1 and derived2 as virtual base classes. This is shown in the following series.
#include
class base
{
protected:
int data;
public:
base()
{
data=10;
}
};
class resulting 1 : virtual public base
{};
class resulting 2 : virtual public base
{};
class resulting 3 : public resulting 1,public resulting 2
{
public:
int getdata()
{
return data;
}
};
void main()
{
derived3 d3;
int val=d3.getdata();
cout<<val<<endl;
}
By means of the keyword virtual in the two classes resulting 1 and resulting 2 makes them share a single subobject of the base class hence eliminating all ambiguity since there is only one subobject for resulting 3 to access. Hence resulting 1 and resulting 2 are known as virtual base classes.
Various other facts
- In order to actualize a Virtual Function the function must be present in that base class even though it is declared virtual in the derived class.
- Virtual Destructors can also be used and they allow execution of the derived destructor first and then it calls itself.
- If b is a base class pointer and d is a derived class pointer then b=d will copy only the base class contents and remove the derived class contents. This is known as Object Slicing and should be avoided.
- If a Virtual Function is called within a derived classes constructor or destructor then the derived function is always called.
Have a nice time!