First, let's start with a short demonstration of what late binding is, does, and accomplishes:
#include <iostream>
class base {
public:
virtual void f() { std::cout << "base::f()\n"; }
void g() { std::cout << "base::g()\n"; }
virtual public ~base() {}
};
class derived : public base {
public:
virtual void f() { std::cout << "derived::f()\n"; }
virtual void g() { std::cout << "derived::g()\n"; }
};
int main() {
base *b = new base; // first case: pointer to base, base object
derived *d = new derived; // second case: pointer to derived, derived object
base *bd = new derived; // third case: pointer to base, derived object
b->f(); // invokes base::f
b->g(); // invokes base::g
d->f(); // invokes derived::f
d->g(); // invokes derived::g
bd->f(); // invokes derived::f
bd->g(); // invokes base::g
}
So in this case, note that f
is a virtual function, and g
is a non-virtual function.
When we invoke g
, the function that gets invoked is determined by the type of the pointer (or reference) we start from. So, if we use a pointer to base, we invoke the base class function, and if we use a pointer to derived, we invoke the derived class function. So, being non-virtual, g
is early-bound. That means the function that will be invoked is determined at compile time based solely upon the type of the pointer being used.
The first two cases with f
aren't much more interesting. If we have a pointer to the base class pointing to an object of the base type, when we call f()
we (of course) get base::f()
, just like we did with g()
. Likewise, when we have a pointer to the derived type, and we invoke f()
, we get the derived::f()
. Not much to see yet.
The third case is where things get interesting: we have a pointer to the base type but it's referring to an object of the derived type. As noted above, when we invoke g()
, the compiler determines the function to invoke based on the type of the pointer, so we invoke base::g()
. But with f()
(because it's marked virtual
) we get late binding. The compiler determines what f()
to call based not on the type of the pointer, but instead on the type of the object it points at. Even though we have a base-class pointer, we invoke the function in the derived class, because we're dealing with an object of the derived class.
As to how this is done: at least in C++, it's normally done using a vtable. When you invoke a member function, your function receives a hidden parameter named this
. If the class defines (non-static) member variables, those variables are accessed by taking an offset from the address in the this
pointer--i.e., a pointer to the data storage for the object. If the class contains at least one virtual function, the object will also contain a (hidden) pointer to a vtable for that class. So let's consider code like this:
class A {
int x;
public:
virtual void foo() { std::cout << "base::foo()\n"; }
virtual void bar() = 0;
virtual ~A() {}
};
class B : public A {
int y;
public:
virtual void bar() { std::cout << "Derived::bar()"; }
virtual void baz() { std::cout << "Added function"; }
};
int main() {
A a;
B b;
}
This will typically be laid out in memory something like this:

So, when we invoke member function, it receives this
as a hidden parameter. That points to the a
or b
object in memory. When we invoke a virtual member function, the compiler generates code to get the correct offset in that object for the vtable pointer. Then it dereferences the vtable pointer to get the vtable for the type of object being referenced. Finally, it looks at a specified offset in the vtable to get to the correct function being invoked.