Project 10.03 Section 10 ⚡ Embedded Relevance: High OOP Virtual Destructors Object Pools Member Initializer Deterministic Memory

10.03 Class Hierarchies, Member Initializers & Deterministic Object Pooling

Executive Summary: Building complete class hierarchies with character progression systems. We analyze constructor member initialization lists, virtual destructor safety against memory leaks, and replacing dynamic heap allocations with static deterministic object pools in safety-critical firmware.

💻 1. Annotated Source Code

#include <iostream>
#include <string>
#include <vector>
#include "Player.h"
#include "Warrior.h"
#include "Priest.h"
#include "Mage.h"
using namespace std;

void printMainMenu();
void printRaceMenu();
Race getRace(int raceNum);
Player* createPlayer(string name, int typeNum, int raceNum);
void printAll(const vector<Player*>& playerList);
void doCleanup(vector<Player*>& playerList);

int main() {
    int choice;
    int raceNum;
    string playerName;
    vector<Player*> playerList;

    printMainMenu();
    cin >> choice; 
    cin.get();

    while (choice != 0) {
        cout << "What would you like to name your character?" << endl;
        getline(cin, playerName);

        printRaceMenu();
        cin >> raceNum;
        cin.get();

        Player* tempPlayer = createPlayer(playerName, choice, raceNum);
        playerList.push_back(tempPlayer);

        printMainMenu();
        cin >> choice;
        cin.get();
    }//end while

    printAll(playerList);
    doCleanup(playerList);

    cout << "Program done!" << endl;

    return 0;
}

void printMainMenu() {
    cout << "\nChoose a class:\n"
        << "\t1 - Warrior\n"
        << "\t2 - Priest\n"
        << "\t3 - Mage\n"
        << "\t0 - Finish\n";
}//end printMainMenu

void printRaceMenu() {
    cout << "Choose a race:\n"
        << "\t1 - Human\n"
        << "\t2 - Elf\n"
        << "\t3 - Dwarf\n"
        << "\t4 - Orc\n"
        << "\t5 - Troll\n";
}//end printRaceMenu

Race getRace(int raceNum) {
    switch (raceNum) {
        case 1: return Race::HUMAN;
        case 2: return Race::ELF;
        case 3: return Race::DWARF;
        case 4: return Race::ORC;
        case 5: return Race::TROLL;
        default: return Race::HUMAN;
    }
}//end getRace

Player* createPlayer(string name, int typeNum, int raceNum) {
    Race race = getRace(raceNum);
    switch (typeNum) {
        case 1: return new Warrior(name, race);
        case 2: return new Priest(name, race);
        case 3: return new Mage(name, race);
        default: return nullptr;
    }
}

void printAll(const vector<Player*>& playerList) {
    for (const Player* player : playerList) {
        cout << "My name is "<<player->getName()
            << ". I'm a " << player->whatRace()
            << " and my attack is: " << player->attack() << endl;
    }
}

void doCleanup(vector<Player*>& playerList) {
    for (Player* player : playerList) {
        delete player;
    }
    playerList.clear();
}
#ifndef PLAYER_H
#define PLAYER_H

#include <string>
using namespace std;

enum class Race { HUMAN, ELF, DWARF, ORC, TROLL };


class Player {
public:
	Player(string name, Race race, int hitPoints, int magicPoints);
	virtual ~Player() = default;

	string getName() const;
	Race getRace() const;
	string whatRace() const;
	int getHitPoints() const;
	int getMagicPointers() const;

	void setName(string name);
	void setRace(Race race);
	void setHitPoints(int hitPoints);
	void setMagicPoints(int magicPoints);

	virtual string attack() const = 0;

private:
	string name;
	Race race;
	int hitPoints;
	int magicPoints;
};

#endif 
#include "Player.h"

Player::Player(string name, Race race, int hitPoints, int magicPoints) 
 : name(name), race(race), hitPoints(hitPoints), magicPoints(magicPoints){
}

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

Race Player::getRace() const {
	return race;
}

string Player::whatRace() const {
	switch(race) {
		case Race::HUMAN:
			return "Human";
		case Race::ELF:
			return "Elf";
		case Race::DWARF:
			return "Dwarf";
		case Race::ORC:
			return "Orc";
		case Race::TROLL:
			return "Troll";
		default:
			return "Unknown";
	}
}

int Player::getHitPoints() const {
	return hitPoints;
}

int Player::getMagicPointers() const {
	return magicPoints;
}

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

void Player::setRace(Race race) {
	this->race = race;
}

void Player::setHitPoints(int hitPoints) {
	this->hitPoints = hitPoints;
}

void Player::setMagicPoints(int magicPoints) {
	this->magicPoints = magicPoints;
}
#ifndef MAGE_H
#define MAGE_H

#include "Player.h"

class Mage : public Player {
	public:	
		Mage(string name, Race race) : Player(name, race, 150, 150) {}

		string attack() const override {
			return "I will crush you with the power of my arcane missiles!";
		}
};

#endif 
// File not found: section_10/RPGProject/RPGProject/Mage.cpp
#ifndef WARRIOR_H
#define WARRIOR_H
#include "Player.h"

class Warrior : public Player {

	public:
		Warrior(string name, Race race) : Player(name, race, 200, 0) {}

		string attack() const override {
			return "I will destroy you with my sword, foul demon!";
		}
};

#endif 
// File not found: section_10/RPGProject/RPGProject/Warrior.cpp
#ifndef PRIEST_H
#define PRIEST_H

#include "Player.h"

class Priest : public Player {
	public:
		Priest(string name, Race race) : Player(name, race, 100, 200) {}

		string attack() const override {
			return "I will assault you with Holy Wrath!";
		}
};

#endif 
// File not found: section_10/RPGProject/RPGProject/Priest.cpp

📐 2. Architecture & UML Class Model

📐 RPG Polymorphic Character Hierarchy & VTable Architecture
+ Public - Private # Protected
<<enum class : uint8_t>> Race Character Race
+HUMAN : uint8_t = 0
+ELF : uint8_t = 1
+DWARF : uint8_t = 2
+ORC : uint8_t = 3
+TROLL : uint8_t = 4
<<abstract class>> Player Base Class (vtable owner)
#_vptr : void** (4 bytes)
#name : std::string
#race : Race
#hitPoints : int32_t
#magicPoints : int32_t
+Player(name: string, race: Race, hp: int, mp: int)
+getName() : std::string const
+getRace() : Race const
+getHitPoints() : int32_t const
+getMagicPoints() : int32_t const
+setName(name: string) : void
+setRace(race: Race) : void
+setHitPoints(hp: int) : void
+setMagicPoints(mp: int) : void
+attack() : std::string[pure virtual =0]
+~Player()[virtual]
<<class>> Warrior Melee Subclass
(none / stateless)
+Warrior(name: string, race: Race)
+attack() : std::string[override]
<<class>> Mage Caster Subclass
(none / stateless)
+Mage(name: string, race: Race)
+attack() : std::string[override]
<<class>> Priest Healer Subclass
(none / stateless)
+Priest(name: string, race: Race)
+attack() : std::string[override]
🔗 Architectural Relationships & Hierarchy
Player ◆── holds race ◆── Race
Warrior ──▷ inherits (virtual attack) ──▷ Player
Mage ──▷ inherits (virtual attack) ──▷ Player
Priest ──▷ inherits (virtual attack) ──▷ Player

📚 3. Core C++ Concepts Deep-Dive

1. Member Initializer Lists

Initializing member variables in constructor initialization lists directly initializes members rather than default-constructing and then assigning, eliminating redundant operations.

📐 RPG Class Hierarchy UML Architecture

<<base>> Player
# name : string
# hitPoints : int
# magicPoints : int
+ attack() : string [virtual]
+ ~Player() [virtual]
<<specialization>> Warrior : public Player
+ attack() : string [override]
<<specialization>> Mage : public Player
+ attack() : string [override]
<<specialization>> Priest : public Player
+ attack() : string [override]

2. Virtual Destructors

When deleting a derived class object through a base class pointer (Base* ptr = new Derived(); delete ptr;), the base class destructor must be virtual; otherwise, the derived class destructor is not called, causing silent resource leaks.

⚡ 4. Embedded Systems & Hardware Reality

1. Static Object Pooling vs Dynamic Heap Allocation

In safety-critical avionics and medical devices (DO-178C / IEC 62304), dynamic heap allocation during runtime is prohibited due to fragmentation and non-deterministic allocation latency. Pre-allocating objects in a Fixed-Block Object Pool guarantees $O(1)$ allocation time and zero fragmentation.

💡 5. Production-Ready Embedded Refactoring

Fixed-capacity deterministic static object pool:

💡 Production-Ready Refactor
#include <cstdint>
#include <array>

template <typename T, size_t Capacity>
class StaticObjectPool {
private:
    alignas(T) uint8_t storage_[Capacity][sizeof(T)];
    std::array<bool, Capacity> in_use_{};

public:
    template <typename... Args>
    T* allocate(Args&&... args) noexcept {
        for (size_t i = 0; i < Capacity; ++i) {
            if (!in_use_[i]) {
                in_use_[i] = true;
                return new (&storage_[i]) T(std::forward<Args>(args)...); // Placement new
            }
        }
        return nullptr; // Pool exhausted (predictable failure!)
    }
};

📝 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 happens if a base class does NOT have a virtual destructor and a derived object is deleted via a base class pointer?
A Undefined behavior: the derived class destructor is not executed, leading to resource/memory leaks
B The compiler generates a compilation error
C The CPU automatically calls free() on all members
D The object is moved to Flash memory
Detailed Explanation: Without a virtual destructor, delete base_ptr performs static binding, calling only the base destructor and leaving derived members uncleaned.
Q2. Why are constructor Member Initializer Lists preferred over assignment inside the constructor body?
A They construct members directly in-place, avoiding redundant default-construction followed by assignment
B They allow calling virtual functions safely
C They place objects in the CPU cache
D They disable compiler optimizations
Detailed Explanation: Initializer lists construct members directly with their target arguments, preventing duplicate default constructor + copy assignment overhead.
Q3. Why do safety-critical standards like MISRA C++ and DO-178C ban dynamic heap allocation (malloc/new) during real-time operation?
A Heap allocation is non-deterministic (variable allocation time) and can fail due to memory fragmentation over extended operational lifetimes
B Heap memory is slower than Flash memory
C ARM microcontrollers cannot run code with pointers
D Heap memory requires high voltage
Detailed Explanation: Heap managers have non-deterministic $O(N)$ worst-case search times and suffer from fragmentation, which can cause sudden allocation failures in mission-critical firmware.
Q4. What C++ mechanism allows constructing an object inside pre-allocated static memory without calling the heap allocator?
A Placement new: new (buffer_ptr) Type(args...)
B reinterpret_cast
C std::malloc
D static_cast
Detailed Explanation: Placement new constructs an object in-place at a specified memory address without allocating heap memory.