Module 04: Polymorphism, Abstract Classes, and Interfaces
Key Concepts:
- Virtual functions
- Runtime polymorphism
- Virtual destructors
- Abstract classes (pure virtual)
- Interfaces
- Deep copy with polymorphism
1. The Problem Without Polymorphism
Section titled “1. The Problem Without Polymorphism”class Animal {public: void makeSound() { std::cout << "Some sound" << std::endl; }};
class Dog : public Animal {public: void makeSound() { std::cout << "Woof!" << std::endl; }};
class Cat : public Animal {public: void makeSound() { std::cout << "Meow!" << std::endl; }};
// THE PROBLEM:Animal* pet = new Dog();pet->makeSound(); // Prints "Some sound" - NOT "Woof!"
// Why? Without virtual, C++ uses STATIC binding// It sees Animal* and calls Animal::makeSound()2. Virtual Functions
Section titled “2. Virtual Functions”The Solution
Section titled “The Solution”class Animal {public: virtual void makeSound() { std::cout << "Some sound" << std::endl; }};
class Dog : public Animal {public: void makeSound() { // Overrides virtual function std::cout << "Woof!" << std::endl; }};
// NOW:Animal* pet = new Dog();pet->makeSound(); // Prints "Woof!" - correct!
// Why? With virtual, C++ uses DYNAMIC binding// It checks the actual object type at runtimeHow Virtual Works
Section titled “How Virtual Works”// Simplified view of what happens:
// Without virtual (static binding):// Compiler sees: Animal* ptr// Compiler calls: Animal::makeSound() - decided at compile time
// With virtual (dynamic binding):// Object contains hidden pointer to "vtable" (virtual table)// vtable contains pointers to correct functions for that class// At runtime, correct function is looked up and calledVirtual Table Visualization
Section titled “Virtual Table Visualization”Animal object: Dog object:+----------+ +----------+| vptr ---|--+ | vptr ---|--+| ... | | | ... | |+----------+ | +----------+ | v v Animal vtable Dog vtable +------------+ +------------+ | makeSound --|-> Animal::makeSound | makeSound --|-> Dog::makeSound +------------+ +------------+3. Virtual Destructors (CRITICAL!)
Section titled “3. Virtual Destructors (CRITICAL!)”The Problem
Section titled “The Problem”class Animal {public: ~Animal() { std::cout << "Animal destroyed" << std::endl; }};
class Dog : public Animal {private: Brain* _brain;public: Dog() { _brain = new Brain(); } ~Dog() { delete _brain; std::cout << "Dog destroyed" << std::endl; }};
Animal* pet = new Dog();delete pet; // ONLY calls ~Animal()! // Dog's brain is LEAKED!The Solution
Section titled “The Solution”class Animal {public: virtual ~Animal() { std::cout << "Animal destroyed" << std::endl; } //^^^^^^^ ALWAYS make destructors virtual in base classes};
Animal* pet = new Dog();delete pet; // Calls ~Dog() THEN ~Animal() - correct!If a class has ANY virtual functions, make its destructor virtual.
4. Abstract Classes
Section titled “4. Abstract Classes”What is an Abstract Class?
Section titled “What is an Abstract Class?”A class that CANNOT be instantiated. It serves as a blueprint for derived classes.
Pure Virtual Functions
Section titled “Pure Virtual Functions”class Animal {public: // Pure virtual function - no implementation virtual void makeSound() const = 0; // ^^^ Makes it pure virtual
virtual ~Animal() {}};
// Now Animal is abstract:Animal pet; // ERROR: cannot instantiate abstract classAnimal* ptr; // OK: can have pointer to abstract classptr = new Dog(); // OK: can point to concrete derived classConcrete Derived Classes
Section titled “Concrete Derived Classes”class Dog : public Animal {public: // MUST implement ALL pure virtual functions void makeSound() const { std::cout << "Woof!" << std::endl; }};
Dog dog; // OK: Dog is concrete (implements all pure virtuals)Partially Abstract Classes
Section titled “Partially Abstract Classes”class Animal {public: virtual void makeSound() const = 0; // Pure virtual virtual void eat() { /* default */ } // Virtual with default};
class Dog : public Animal {public: void makeSound() const { /* ... */ } // Must implement // eat() inherited with default implementation};5. Interfaces
Section titled “5. Interfaces”What is an Interface?
Section titled “What is an Interface?”A class with ONLY pure virtual functions. Defines a contract without any implementation.
// Interface naming convention: prefix with 'I'class ICharacter {public: virtual ~ICharacter() {} virtual std::string const& getName() const = 0; virtual void equip(AMateria* m) = 0; virtual void unequip(int idx) = 0; virtual void use(int idx, ICharacter& target) = 0;};Implementing an Interface
Section titled “Implementing an Interface”class Character : public ICharacter {private: std::string _name; AMateria* _inventory[4];
public: Character(std::string name); Character(const Character& other); Character& operator=(const Character& other); ~Character();
// Must implement ALL interface methods std::string const& getName() const; void equip(AMateria* m); void unequip(int idx); void use(int idx, ICharacter& target);};6. Deep Copy with Polymorphism
Section titled “6. Deep Copy with Polymorphism”The Problem
Section titled “The Problem”class Animal {protected: Brain* _brain;public: Animal() { _brain = new Brain(); } Animal(const Animal& other) { _brain = new Brain(*other._brain); } virtual ~Animal() { delete _brain; }};
class Dog : public Animal { /* ... */ };
// Problem: copying through base pointerAnimal* original = new Dog();Animal* copy = new Animal(*original); // Creates Animal, not Dog!The Clone Pattern
Section titled “The Clone Pattern”class Animal {public: virtual Animal* clone() const = 0; virtual ~Animal() {}};
class Dog : public Animal {public: Dog* clone() const { return new Dog(*this); }};
Animal* original = new Dog();Animal* copy = original->clone(); // Creates Dog!Clone Pattern: Full Implementation
Section titled “Clone Pattern: Full Implementation”class AMateria {protected: std::string _type;public: AMateria(std::string const& type) : _type(type) {} virtual ~AMateria() {}
std::string const& getType() const { return _type; } virtual AMateria* clone() const = 0; // Pure virtual};
class Ice : public AMateria {public: Ice() : AMateria("ice") {} Ice(const Ice& other) : AMateria(other) {}
// Covariant return type - return Ice* instead of AMateria* Ice* clone() const { return new Ice(*this); // Uses copy constructor }};
class Cure : public AMateria {public: Cure() : AMateria("cure") {} Cure(const Cure& other) : AMateria(other) {}
Cure* clone() const { return new Cure(*this); }};Memory Management with Clone
Section titled “Memory Management with Clone”class Character {private: AMateria* _inventory[4];public: // Deep copy using clone Character(const Character& other) { for (int i = 0; i < 4; i++) { if (other._inventory[i]) _inventory[i] = other._inventory[i]->clone(); else _inventory[i] = NULL; } }
// Assignment using clone Character& operator=(const Character& other) { if (this != &other) { // Delete old inventory for (int i = 0; i < 4; i++) delete _inventory[i]; // Clone new inventory for (int i = 0; i < 4; i++) { if (other._inventory[i]) _inventory[i] = other._inventory[i]->clone(); else _inventory[i] = NULL; } } return *this; }
~Character() { for (int i = 0; i < 4; i++) delete _inventory[i]; }};7. Arrays of Base Class Pointers
Section titled “7. Arrays of Base Class Pointers”Creating and Managing Polymorphic Arrays
Section titled “Creating and Managing Polymorphic Arrays”// Array of Animal pointers (can hold Dogs, Cats, etc.)Animal* animals[4];
animals[0] = new Dog();animals[1] = new Cat();animals[2] = new Dog();animals[3] = new Cat();
// Polymorphic behaviorfor (int i = 0; i < 4; i++) { animals[i]->makeSound(); // Calls correct version}
// CRITICAL: Must delete each elementfor (int i = 0; i < 4; i++) { delete animals[i]; // Virtual destructor ensures proper cleanup}Why Virtual Destructor is Critical
Section titled “Why Virtual Destructor is Critical”// WITHOUT virtual destructor:Animal* pet = new Dog(); // Dog allocates Braindelete pet; // Only ~Animal() called - Brain leaked!
// WITH virtual destructor:Animal* pet = new Dog(); // Dog allocates Braindelete pet; // ~Dog() called first (deletes Brain), then ~Animal()8. Module 04 Exercise Structure
Section titled “8. Module 04 Exercise Structure”ex00: Polymorphism
Section titled “ex00: Polymorphism”class Animal {protected: std::string _type;public: Animal(); Animal(const Animal& other); Animal& operator=(const Animal& other); virtual ~Animal();
virtual void makeSound() const; std::string getType() const;};
class Dog : public Animal {public: Dog(); Dog(const Dog& other); Dog& operator=(const Dog& other); ~Dog();
void makeSound() const; // Barks};
class Cat : public Animal {public: Cat(); Cat(const Cat& other); Cat& operator=(const Cat& other); ~Cat();
void makeSound() const; // Meows};
// Also implement WrongAnimal/WrongCat without virtual// to demonstrate the differenceex01: Deep Copy
Section titled “ex01: Deep Copy”class Brain {public: std::string ideas[100];
Brain(); Brain(const Brain& other); Brain& operator=(const Brain& other); ~Brain();};
class Dog : public Animal {private: Brain* _brain; // Dynamically allocatedpublic: Dog(); Dog(const Dog& other); // Must deep copy brain Dog& operator=(const Dog& other); // Must deep copy brain ~Dog(); // Must delete brain};
// Test deep copy:Dog original;Dog copy = original;// Modifying copy's brain should NOT affect original's brainex02: Abstract Class
Section titled “ex02: Abstract Class”// Make Animal abstract (cannot instantiate)class Animal {public: virtual void makeSound() const = 0; // Pure virtual // ...};
Animal pet; // ERROR: Animal is abstractAnimal* ptr = new Dog(); // OKex03: Interfaces (Materia System)
Section titled “ex03: Interfaces (Materia System)”class AMateria {protected: std::string _type;public: AMateria(std::string const& type); virtual ~AMateria();
std::string const& getType() const; virtual AMateria* clone() const = 0; virtual void use(ICharacter& target);};
class Ice : public AMateria {public: Ice(); Ice(const Ice& other); Ice& operator=(const Ice& other); ~Ice();
AMateria* clone() const; void use(ICharacter& target);};
class Cure : public AMateria { // Similar to Ice};
class Character : public ICharacter {private: std::string _name; AMateria* _inventory[4];public: // Implement all ICharacter methods // Handle equip/unequip memory carefully!};
class MateriaSource : public IMateriaSource { // Learn and create Materias};9. Common Patterns
Section titled “9. Common Patterns”Factory Pattern (MateriaSource)
Section titled “Factory Pattern (MateriaSource)”The Factory Pattern creates objects without exposing instantiation logic.
class IMateriaSource {public: virtual ~IMateriaSource() {} virtual void learnMateria(AMateria*) = 0; virtual AMateria* createMateria(std::string const& type) = 0;};
class MateriaSource : public IMateriaSource {private: AMateria* _templates[4];
public: MateriaSource() { for (int i = 0; i < 4; i++) _templates[i] = NULL; }
// Deep copy in copy constructor MateriaSource(const MateriaSource& other) { for (int i = 0; i < 4; i++) { if (other._templates[i]) _templates[i] = other._templates[i]->clone(); else _templates[i] = NULL; } }
~MateriaSource() { for (int i = 0; i < 4; i++) delete _templates[i]; }
void learnMateria(AMateria* m) { if (!m) return; for (int i = 0; i < 4; i++) { if (_templates[i] == NULL) { _templates[i] = m->clone(); // Store a COPY return; } } }
// Factory method - creates new objects based on type string AMateria* createMateria(std::string const& type) { for (int i = 0; i < 4; i++) { if (_templates[i] && _templates[i]->getType() == type) return _templates[i]->clone(); // Return a NEW copy } return NULL; }};Factory Usage
Section titled “Factory Usage”IMateriaSource* src = new MateriaSource();
// Teach the factory what it can createsrc->learnMateria(new Ice());src->learnMateria(new Cure());
// Factory creates new instancesAMateria* ice = src->createMateria("ice"); // New Ice objectAMateria* cure = src->createMateria("cure"); // New Cure objectAMateria* unknown = src->createMateria("fire"); // NULL - not learned
delete src;Key Factory Pattern Points
Section titled “Key Factory Pattern Points”- Decouples creation from usage: Client doesn’t need to know concrete types
- Uses clone(): New objects are copies of templates
- Memory ownership: Factory owns templates, caller owns created objects
Quick Reference
Section titled “Quick Reference”Virtual Function Syntax
Section titled “Virtual Function Syntax”virtual void method(); // Virtual (can override)virtual void method() = 0; // Pure virtual (must override)void method(); // Non-virtual (hides, doesn't override)Abstract Class
Section titled “Abstract Class”- Has at least one pure virtual function
- Cannot be instantiated
- Can have data members and non-pure methods
Interface
Section titled “Interface”- Only pure virtual functions
- Virtual destructor
- No data members (typically)
- Defines a contract
Memory Safety Checklist
Section titled “Memory Safety Checklist”- Virtual destructor in base class
- Deep copy in copy constructor
- Deep copy in assignment operator
- Delete allocated memory in destructor
- Handle unequip() without deleting (save pointer first)