Virtual Functions & Runtime Polymorphism in C++

Virtual Functions & Runtime Polymorphism in c++
Virtual Functions & Runtime Polymorphism in c++

Introduction

The virtual functions concept is used to achieve runtime polymorphism in C++. The problem of function overriding leads to this concept. So, Let’s understand the problem and solutions to it in deep.

What is overriding?
Method overriding is a way of declaring a method in the subclass which is already present in the parent class is known. Overriding is done when a child class wants to do something to which is done in the parent class. Now, the method in the parent class is called the overridden method and the method in the child class is called overriding method. When a derived class object is created and the function is called the compiler gets confused to execute which function (either base or derived), it then executes the derived class method. To avoid function overriding, we use virtual keyword.

Example

include <iostream>

using namespace std;
class Base

{
public:
void show_val()
{
cout << “Class::Base”<<endl;
}
};
class Derived: public Base
{
public:
void show_val() //function overridden from base
{
cout << “Class::Derived”<<endl;
}
};
int main()
{
Base b;
Derived d;
b.show_val();

d.show_val();
}

Output:
Class:: Base
Class:: Derived

Facts:

  • Function overloading is an example of compile-time polymorphism
  • Function overriding is an example of run time polymorphism

Early Binding
This is compile-time polymorphism. Here, it directly associates an address to the function call. For function overloading, it is an example of early binding.

Example

include<iostream>

using namespace std;
class Base {
public:
void display() {
cout<<” In Base class” <<endl;
}
};
class Derived: public Base {
public:
void display() {
cout<<“In Derived class” << endl;
}
};
int main(void) {
Base *base_pointer = new Derived;
base_pointer->display();
return 0;

}
Output
In Base class

Late Binding
This is run time polymorphism. In this type of binding, the compiler adds code that identifies the object type at runtime then matches the call with the right function definition. This is achieved by using the virtual function.

Example

include<iostream>

using namespace std;
class Base {
public:
virtual void display() {
cout<<“In Base class” << endl;
}
};
class Derived: public Base {

public:
void display() {
cout<<“In Derived class” <<endl;
}
};
int main() {
Base *base_pointer = new Derived;
base_pointer->display();
return 0;
}
Output
In Derived class

Virtual Functions
A virtual function is a member function of a base class which is redefined in the derived class. It is achieved by using the keyword ‘virtual’ in the base class. The function call is decided on the type of referred object not according to the type of pointer.

Rules for Virtual Functions:

  • Virtual functions cannot be static and friend to another class
  • Virtual functions must be accessed using pointers or references of base class type
  • The function prototype should be same in both base and derived classes
  • A class must not have a virtual constructor. But it can have a virtual destructor
  • They are always defined in the base class and redefined in the derived class

Example

include <iostream>

using namespace std;
class base {
public:
virtual void print()
{
cout << “print base class” << endl;
}

void show()

{
cout << “show base class” << endl;
}
};

class derived : public base {
public:
void print()
{
cout << “print derived class” << endl;
}

void show()
{
cout << “show derived class” << endl;
}
};

int main()
{

base* bptr;
derived d;
bptr = &d;
// virtual function, binded at runtime
bptr->print();
// Non-virtual function, binded at compile time
bptr->show();
}

Output:
print derived class
show base class

Explanation: Runtime polymorphism is achieved only through a pointer (or reference) of base class type. Also, a base class pointer can point to the objects of the base class as well as to the objects of the derived class. In the above code, base class pointer ‘bptr’ contains the address of object ‘d’ of the derived class.
Late binding(Runtime) is done in accordance with the content of pointer (i.e. location pointed to by pointer) and Early binding (Compile-time) is done according to the type of pointer since print() function is declared with the virtual keyword so it will be bound at run-time (output is print derived class as a pointer is pointing to object of derived class ) and show() is non-virtual so it will be bound during compile time(output is show
base class as a pointer is of base type).


NOTE: If we have created a virtual function in the base class and it is being overridden in the derived class then we don’t need virtual keyword in the derived class, functions are automatically considered as virtual functions in the derived class.

Pure Virtual Functions
A pure virtual function is a virtual function in C++ for which we need not write any function definition and only we have to declare it. It is declared by assigning 0 in the declaration. An abstract class is a class in C++ which have at least one pure virtual function.

An abstract class can have normal functions and variables along with a pure virtual function.

  • An abstract class cannot be instantiated, but pointers and references of Abstract class type can be created
  • Abstract classes are mainly used for Upcasting so that its derived classes can use their interface
  • If an Abstract Class has derived class, they must implement all pure virtual functions, or else they will become Abstract too
  • We can’t create an object of abstract class as we reserve a slot for a pure virtual function in Vtable, but we don’t put any address, so Vtable will remain incomplete

Example

include<iostream>

using namespace std;
class B {
public:
virtual void s() = 0; // Pure Virtual Function
};

class D:public B {
public:
void s() {
cout << “Virtual Function in Derived class\n”;
}
};

int main() {
B *b;
D dobj;
b = &dobj;

b->s();
}
Output

Virtual Function in Derived class

Virtual Destructors
Destructors of the class can be declared as virtual. Whenever we do upcast i.e. assigning the derived class object to a base class pointer, the ordinary destructors can produce unacceptable results.

For Example, consider the following upcasting of the ordinary destructor.

include <iostream>

using namespace std;
class Base
{
public:
~Base()
{
cout << “Base Class:: Destructor\n”;

}
};
class Derived: public Base
{
public:
~Derived()
{
cout<< “Derived class:: Destructor\n”;
}
};
int main()
{
Base* b = new Derived; // Upcasting
delete b;
}

Output:
Base Class:: Destructor

In the above program, we have an inherited derived class from the base class. In the main, we assign an object of the derived class to a base class pointer. Ideally, the destructor that is called when “delete b” is called should have been that of derived class but we can see from the output that destructor of the base class is called as base class pointer points to that.

Due to this, the derived class destructor is not called and the derived class object remains intact thereby resulting in a memory leak. The solution to this is to make base class constructor virtual so that the object pointer points to correct destructor and proper destruction of objects is carried out.

The use of virtual destructor is shown in the below example.

include <iostream>

using namespace std;
class Base
{
public:
virtual ~Base()
{
cout << “Base Class:: Destructor\n”;

}
};
class Derived: public Base
{
public:
~Derived()
{
cout<< “Derived class:: Destructor\n”;
}
};
int main()
{
Base* b = new Derived; // Upcasting
delete b;
}

Output:
Derived class:: Destructor
Base Class:: Destructor

To read more on C++, click here.

By Mansi Agarwal