Early Binding vs Late Binding in C++: vtable and vptr Explained

Binding means deciding which function should be called for a particular function call. In C++, this decision can happen either at compile time or at run time.
When the function call is resolved at compile time, it is called Early Binding or Static Binding.
When the function call is resolved at run time based on the actual object type, it is called Late Binding or Dynamic Binding.

Let’s look into the differences first.

Early BindingLate Binding
Happens at compile time.Happens at run time.
Also called Static BindingAlso called Dynamic Binding
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 bindingFunctions 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.

The important point here is that simply calling a function through a pointer does not automatically make it late binding.
Late binding happens when a virtual function is called through a suitable base-class pointer or reference.

Example of Early Binding

Lets look into a simple example

#include <iostream>

using namespace std;

class Base
{
public:
    void print()
    {
        cout << "Base version called." << endl;
    }
};

class Derived : public Base
{
public:
    void print()
    {
        cout << "Derived version called." << endl;
    }
};

int main()
{
    Base* ptr = new Derived();

    ptr->print();

    delete ptr;
}

Since print() is not virtual, the function call is resolved using the static type of ptr, which is Base*.
The output will be:

Base version called.

Example of Late Binding

Now let’s make the same function virtual.

#include <iostream>

using namespace std;

class Base
{
public:
    virtual ~Base() = default;

    virtual void print()
    {
        cout << "Base version called." << endl;
    }
};

class Derived : public Base
{
public:
    void print() override
    {
        cout << "Derived version called." << endl;
    }
};

int main()
{
    Base* ptr = new Derived();

    ptr->print();

    delete ptr;
}

Now the output will be:

Derived version called.

Even though ptr is of type Base*, it actually points to a Derived object.
Since print() is virtual, the function to be called is resolved at run time and the Derived version is invoked.
This is late binding.

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.

C++ compilers commonly implement virtual functions using a Virtual Table, usually called a vtable, and a hidden virtual pointer, usually called a vptr.

Let’s have a look at the below diagram to understand how virtual table and virtual pointer works.

Important Points About vtable and vptr

  • 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

Example with Virtual Functions

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

The exact object sizes are implementation-dependent, so you should not rely on 8 and 1 being the same on every compiler or platform.
The important point is that classes using virtual dispatch commonly require additional hidden implementation data.

Why Should a Base Class Destructor Be Virtual?

If an object of a derived class is deleted through a base-class pointer, the base class should usually have a virtual destructor.

Base* ptr = new Derived();

delete ptr;

// With a virtual destructor, both destructors are called in the proper order:
Derived destructor
Base destructor

This allows the complete object to be cleaned up correctly.

Early Binding vs Late Binding: Which One Should You Use?

This is not usually a choice between one being “better” than the other.
Use virtual functions when different derived classes need to provide different implementations through a common base-class interface else use normal functions.

For example:

class Shape
{
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};

Different classes such as Circle, Rectangle, and Triangle can implement area() differently while still being accessed through a Shape* or Shape&.
That is where late binding becomes useful.

Summary

Early binding resolves the function call at compile time, while late binding resolves the final virtual-function implementation at run time. In C++

  • normal non-virtual calls generally use early binding;
  • virtual calls through base-class pointers or references can use late binding;
  • vtables and vptrs are common compiler techniques used to implement virtual dispatch;
  • their exact layout is compiler and ABI dependent;
  • virtual functions provide run-time polymorphism, but they also introduce a small amount of run-time and object-layout overhead.

The main idea is simple:

Non-virtual function  -> usually resolved at compile time
Virtual function      -> may be resolved at run time

Leave a Reply

Your email address will not be published. Required fields are marked *