Early Binding | Late Binding |
Happens at compile time. | Happens at run time. |
Compiler has all information to invoke correct function version at compile time. | Compiler doesn’t have information to identify correct function version. |
Normal function calls are example of Early Binding. | Virtual functions are example of Late Binding. |
Functions invoked by object is example of early binding | Functions invoked by pointers can be example of late binding. |
Early binding is efficient as no cost paid at run-time for function resolution. | Late Binding is slightly costly as function resolution happens at run time. |
Early binding doesn’t provide flexibility of one method multiple interfaces. | Late Binding provide flexibility. |
Doesn’t have any virtual table or virtual pointer. | It uses Virtual Table and Virtual Pointer. |
Virtual Table and Virtual Pointer
Whenever a class has virtual function, a virtual table is created for that class by the compiler. This virtual table contains all the virtual function addresses of that class. A virtual pointer is also defined by the compiler to access this virtual table at run time to identify correct version of function to be called.
Let’s have a look at the below diagram to understand how virtual table and virtual pointer works.
Important Points
- Whenever a virtual function is invoked by pointer or reference, virtual table is used to resolve the function address.
- There is only one Virtual Table for a class and all the objects share the same virtual table.
- Compiler adds a virtual pointer for all objects, which points to corresponding virtual table.
- Object of a class having virtual functions will have more size than normal version of class object. This is because each object has one extra virtual pointer to point virtual table.
- To find correct function address, virtual pointer uses virtual table as it contains function addresses of all virtual function defined by class
Let’s have a look at the sample program to understand it better.
#include <iostream>
using namespace std;
class BaseWithoutVirtual
{
public:
~BaseWithoutVirtual ()
{
cout << "Inside BaseWithoutVirtual Destructor." << endl;
}
void print ()
{
cout << "BaseWithoutVirtual version called." << endl;
}
void pure_virtual_fun ()
{}
};
class Base
{
public:
virtual ~Base ()
{
cout << "Inside Base Destructor." << endl;
}
virtual void print ()
{
cout << "Base version called." << endl;
}
virtual void pure_virtual_fun () = 0;
};
class Derived:public Base
{
public:
virtual ~Derived ()
{
cout << "Inside Derived Destructor." << endl;
}
void print () /* virtual keyword not needed */
{
cout << "Derived version called." << endl;
}
void pure_virtual_fun ()
{
cout << "Derived class version for pure_virtual_fun" << endl;
}
};
int main ()
{
Base *p = new Derived ();
p -> print ();
p -> pure_virtual_fun ();
delete p;
/* Not allowed to create Base class object as it has pure virtual function. */
/*
p = new Base ();
*/
cout << "Derived class object size: " << sizeof (Derived) << endl;
cout << "Base Without Virtual class object size: " << sizeof (BaseWithoutVirtual) << endl;
}
Let’s analyze the output of above program.
Derived version called.
Derived class version for pure_virtual_fun
Inside Derived Destructor.
Inside Base Destructor.
Derived class object size: 8
Base Without Virtual class object size: 1