Project 10.02 Section 10 ⚡ Embedded Relevance: Critical VTable VPtr CRTP RTTI Dynamic Dispatch Flash Overhead

10.02 Runtime Polymorphism vs Zero-Overhead Static Polymorphism (CRTP)

Executive Summary: Exploring abstract classes, pure virtual functions, and dynamic polymorphism. We analyze the underlying VTable and VPtr memory overhead on ARM Cortex-M, why RTTI is disabled in embedded firmware (-fno-rtti), and how to replace dynamic dispatch with zero-cost CRTP (Curiously Recurring Template Pattern).

💻 1. Annotated Source Code

#include <iostream>
#include "Animal.h"
#include "Dog.h"
#include "Cat.h"
using namespace std;

int main() {
	Dog dog("Rover", 80, "Greyhound");

	//Here is the key line:
	Animal* dogPtr = new Dog("Fido", 115, "Golden Retriever");
	Animal* catPtr = new Cat("Felix", 12);

	cout << "Dog says: " << dogPtr->makeNoise() << endl;
	cout << "Dog eats: " << dogPtr->eat() << endl;

	cout << "Cat says: " << catPtr->makeNoise() << endl;
	cout << "Cat eats: " << catPtr->eat() << endl;


	// (Cat*)catPtr or reinterpret_cat<Cat*>(catPtr)
	Cat* realCat = dynamic_cast<Cat*>(catPtr);
	if (realCat) {
		realCat->chaseMouse();
	}

	delete dogPtr;
	dogPtr = nullptr;

	delete catPtr;
	catPtr = nullptr;

	return 0;
}
#ifndef ANIMAL_H
#define ANIMAL_H

#include <string>
using namespace std;

class Animal {

	public:
		Animal(string name, double weight);
		string getName() const;
		void setName(string name);
		double getWeight() const;
		void setWeight(double weight);
		virtual string makeNoise() const = 0;
		virtual string eat() const = 0;

	private:
		string name;
		double weight;
};

#endif 
#include "Animal.h"


Animal::Animal(string name, double weight) {
	this->name = name;
	this->weight = weight;
}

string Animal::getName() const {
	return name;
}

void Animal::setName(string name) {
	this->name = name;
}

double Animal::getWeight() const {
	return weight;
}

void Animal::setWeight(double weight) {
	this->weight = weight;
}
#ifndef DOG_H
#define DOG_H
#include <string>
#include "Animal.h"
using namespace std;

class Dog : public Animal {
	public:
		Dog(string name, double weight, string breed);
		string getBreed() const;
		void digHole() const;
		string makeNoise() const override;
		void chaseCat() const;
		string eat() const override;

	private:
		string breed;
};

#endif 
#include "Dog.h"
#include <iostream>
#include <string>
using namespace std;

Dog::Dog(string name, double weight, string breed) : Animal(name, weight)  {
	this->breed = breed;
}

string Dog::getBreed() const {
	return breed;
}

void Dog::digHole() const {
	cout << "I'm digging a hole!" << endl;
}

string Dog::makeNoise() const {
	return "Woof!";
}

void Dog::chaseCat() const {
	cout << "Here, kitty kitty!" << endl;
}

string Dog::eat() const {
	return "I love dog food!";
}
#ifndef CAT_H
#define CAT_H

#include <string>
#include "Animal.h"
using namespace std;

class Cat : public Animal {
	public:
		Cat(string name, double weight);
		void chaseMouse() const;
		string makeNoise() const override;
		string eat() const override;
};

#endif 
#include "Cat.h"
#include <iostream>
using namespace std;

Cat::Cat(string name, double weight) : Animal(name, weight) {
}

void Cat::chaseMouse() const {
	cout << "I'm chasing a mouse!" << endl;
}

string Cat::makeNoise() const  {
	return "Meow!";
}

string Cat::eat() const {
	return "Tasty kitty food!";
}

📐 2. Architecture & UML Class Model

📐 Virtual Table (vtable/vptr) Polymorphism Architecture
+ Public - Private # Protected
<<abstract class>> Animal Base Class (Has vptr)
#_vptr : void** (4 bytes hidden RAM pointer)
#name : std::string
#weight : double
+Animal(name: string, weight: double)
+getName() : std::string const
+getWeight() : double const
+makeNoise() : std::string[pure virtual =0]
+eat() : void[virtual]
+~Animal()[virtual]
<<class>> Dog Derived Class
-breed : std::string
+Dog(name: string, weight: double, breed: string)
+getBreed() : std::string const
+makeNoise() : std::string[override]
+digHole() : void
+chaseCat() : void
<<class>> Cat Derived Class
(none / stateless)
+Cat(name: string, weight: double)
+makeNoise() : std::string[override]
+chaseMouse() : void
🔗 Architectural Relationships & Hierarchy
Dog ──▷ public inherits (vtable override) ──▷ Animal
Cat ──▷ public inherits (vtable override) ──▷ Animal

📚 3. Core C++ Concepts Deep-Dive

1. Virtual Functions & Dynamic Dispatch

Declaring a method virtual instructs the compiler to perform dynamic dispatch at runtime via a Virtual Method Table (VTable), enabling polymorphic behavior when calling methods through base class pointers.

📐 Polymorphic UML Class Hierarchy

<<abstract>> Animal
# name : string
# weight : double
+ makeNoise() : void = 0
+ eat() : void
+ ~Animal() [virtual]
<<derived>> Dog : public Animal
- breed : string
+ makeNoise() : void [override]
+ chaseCat() : void
<<derived>> Cat : public Animal
+ makeNoise() : void [override]
+ chaseMouse() : void

2. Pure Virtual Functions & Abstract Classes

Declaring a method with = 0 creates an Abstract Base Class (interface) that cannot be instantiated directly, enforcing contract compliance across derived classes.

⚡ 4. Embedded Systems & Hardware Reality

1. The Hidden RAM & Flash Cost of VTables (ARM Architecture)

  • VPtr Overhead: Every class instance with virtual functions includes a hidden 4-byte (or 8-byte on 64-bit) vptr pointer in SRAM. In an array of 1,000 sensor objects, this wastes 4KB of precious RAM!
  • Pointer Chasing Latency: Virtual calls require two memory dereferences (fetch object vptr, fetch function address from VTable in Flash, then branch via BLX), preventing compiler inlining.
  • RTTI Overhead: Run-Time Type Information adds type descriptor structures to Flash. Embedded firmware compiles with -fno-rtti.

💡 5. Production-Ready Embedded Refactoring

Zero-overhead static polymorphism using the Curiously Recurring Template Pattern (CRTP):

💡 Production-Ready Refactor
#include <cstdint>

// CRTP Base Interface: 0 bytes RAM overhead, 0 vtable pointers!
template <typename Derived>
class SensorDriver {
public:
    uint16_t read() noexcept {
        return static_cast<Derived*>(this)->read_impl(); // Fully inlined!
    }
};

class TemperatureSensor : public SensorDriver<TemperatureSensor> {
public:
    uint16_t read_impl() noexcept {
        return 0x0123; // Direct hardware register read
    }
};

📝 Knowledge Verification Quiz

Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.

Q1. What is the memory overhead introduced to every object instance of a class containing virtual functions on a 32-bit ARM microcontroller?
A A 4-byte virtual table pointer (vptr) stored in the object's RAM allocation
B A copy of the entire compiled machine code in SRAM
C 64 bytes of heap metadata
D Zero overhead; virtual functions are resolved entirely at compile time
Detailed Explanation: Each instance of a polymorphic class contains a 4-byte vptr pointing to the class's shared VTable in Flash ROM, increasing object size.
Q2. Why is dynamic virtual function dispatch often avoided in high-frequency embedded interrupt handlers (ISRs)?
A Indirect branch dereferences (pointer chasing) add CPU latency cycles and prevent compiler function inlining
B Microcontrollers do not support pointers
C Virtual functions can only be called from user space threads
D The ARM ALU cannot perform conditional arithmetic
Detailed Explanation: Virtual calls require dereferencing the vptr and table index, introducing indirect branch latency and defeating compiler inlining optimizations.
Q3. What is the Curiously Recurring Template Pattern (CRTP) used for in embedded C++ systems?
A To achieve static (compile-time) polymorphism with zero RAM vptr overhead and complete function inlining
B To dynamically allocate memory from external SPI RAM
C To debug hardware trace registers over JTAG
D To emulate an operating system kernel
Detailed Explanation: CRTP uses template inheritance to resolve polymorphic interface calls at compile time, eliminating VTables, VPtrs, and indirect branch penalties entirely.
Q4. Which compiler flag disables Run-Time Type Information to reduce Flash binary size in embedded systems?
A -fno-rtti
B -fno-exceptions
C -O3
D -nostdlib
Detailed Explanation: The -fno-rtti flag disables generation of typeinfo metadata tables in Flash ROM, reducing binary footprint when dynamic_cast is unused.