Skip to main content

Command Palette

Search for a command to run...

Understanding Virtual Functions in C++

The Foundation of Runtime Polymorphism

Updated
4 min readView as Markdown
S

Satish Waghole Expert Developer | Automotive Software Architect | Tech Enthusiast I’m a seasoned software professional with 15+ years of experience in Automotive Embedded Systems, specializing in C++ development and architecture design. Currently leading an automation team and working on cutting-edge solutions that power the future of mobility. Passionate about clean code, scalable architectures, and innovation in automotive technology, I love sharing insights on software design, performance optimization, and emerging trends in embedded systems. When I’m not coding, I explore new tech stacks, mentor aspiring developers, and write about practical approaches to building robust software.

One of the most powerful features of Object-Oriented Programming (OOP) in C++ is runtime polymorphism, and virtual functions are the key mechanism that enables it.

In this article, we'll explore what virtual functions are, why they are needed, how they work internally, and best practices for using them in real-world software development.

Virtual Functions Deep Dive

Virtual functions enable runtime polymorphism through dynamic dispatch. When invoked through a base pointer or reference, the actual function is selected based on the runtime object type.

Code Example:

class Base { public: virtual void show() {} virtual ~Base() = default; };   class Derived : public Base { public: void show() override {} };

Runtime vs Compile-time Polymorphism

Compile-time polymorphism is achieved through function overloading and templates. Runtime polymorphism uses inheritance and virtual functions.

Code Example:

class Base { public: virtual void show() {} virtual ~Base() = default; };   class Derived : public Base { public: void show() override {} };

Dynamic Binding vs Static Binding

Static binding resolves calls at compile time. Dynamic binding resolves them using the vtable at runtime.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

vTable Architecture

Each polymorphic class gets a compiler-generated virtual table. Objects contain a hidden vptr pointing to that table.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Memory Layout Illustrations

Polymorphic objects store data members plus a hidden vptr. This increases object size slightly.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Virtual Destructor Internals

Deleting a derived object through a base pointer requires a virtual destructor to ensure proper destruction order.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Pure Virtual Functions & Abstract Classes

Using =0 creates an abstract interface and forces derived classes to implement behavior.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Multiple Inheritance + Virtual Functions

A derived class can inherit from multiple bases. Multiple vptrs may exist depending on compiler implementation.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Diamond Problem

When two classes inherit from a common base and another class inherits from both, duplicate base subobjects can occur.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Virtual Inheritance

Virtual inheritance ensures only one shared base subobject exists in a diamond hierarchy.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

override and final

override enforces proper overriding. final prevents further overrides or inheritance.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Object Slicing

Assigning a derived object to a base object by value removes the derived portion.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Automotive Embedded System Example

Diagnostic services, communication stacks, and application frameworks commonly use runtime polymorphism.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Performance Benchmarks

Virtual calls introduce an indirection through vptr/vtable. Cost is typically small but relevant in tight loops.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Modern C++ Best Practices

Use override everywhere, virtual destructors for polymorphic bases, and avoid unnecessary inheritance.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };

Interview Questions

What is a vtable? What is a vptr? Difference between virtual and pure virtual? Why are destructors virtual? What is object slicing? Explain diamond problem.

Code Example:

class Base { public: virtual void show(){} virtual ~Base()=default; }; 
class Derived: public Base { public: void show() override {} };