In this article, we’re going to dive deep into how C++ inheritance looks in memory and how polymorphism works under the hood. This is not an article on best practices and motivations for inheritance, but rather how C++ makes such powerful and fast inheritance tools.

Let’s start with single inheritance.

The Derived class inherits all the member variables and functions of Base, and the objects memory looks like this

The Derived object looks similar to the Base object in memory in the beginning, but then has some extra things that it created like int b.

You might also be wondering where are all the functions are being stored in memory. All function instructions are stored in a special place in memory once, and not per object. When things are compiled, those function calls will point to the location of the function instructions.

How to Override a Function

The next important part of inheritance is the ability for the derived class to override functions defined in the base class. To do this, C++ lets us make functions virtual. When a function is virtual it goes on the virtual table (vtable). The vtable stores function pointers for all the virtual functions. There is one vtable per class and all objects of the class have a vtable pointer to it.

The order of the function pointers from the Base class is the same order as the Derived class. However, the Base vtable’s bar1 function pointer points to Base::bar1 and the Derived vtable’s bar1 function pointer points to Derived::bar1. The Derived vtable will also have all the other virtual functions it created on there, after the virtual functions from the Base class.

A call on a virtual function like d_ptr->bar() is kind of equivalent to *((d_ptr->vtable_ptr)[0])(d_ptr). You don’t actually have access to the vtable_ptr, so this equivalence is theoretical.

  1. d_ptr->vtable_ptr gets the vtable_ptr which points to an array of function pointers.
  2. (d_ptr->vtable_ptr)[0] is the first element on the Derived vtable which gets us a function pointer to Derived::bar1.
  3. *((d_ptr->vtable_ptr)[0]) is the dereferenced function pointer.
  4. *((d_ptr->vtable_ptr)[0])(d_ptr) is calling Derived::bar1 and passing in a reference to the object that called it. The compiler always implicitly passes the “this” pointer to member functions.

Virtual functions get looked up on the object’s vtable at runtime. So unlike regular functions that get marked in at compile time, virtual functions will look up the function on the vtable at runtime, and run whatever function the vtable entry is pointing to. This is a powerful tool in C++ which enables polymorphism. Polymorphism is when you decide somethings functionality at runtime. You can have a pointer to an object call bar1, but the version of bar1 that is actually invoked depends on the object. That’s why the functionality is only determined at runtime.

#inheritance #memories #c++

C++ Inheritance Memory Model
1.35 GEEK