Change Theme
Font Size
# Lecture 17: Multiple Inheritance and Polymorphism *Advanced C++ inheritance patterns, v-tables, and design considerations.*
## Multiple Inheritance: Concept and Syntax **Definition:** A derived class inherits from more than one base class. **Syntax example:** ```cpp class FlyingCar : public Car, public Airplane { /*...*/ }; ```` --- **Advantages:** * Composition of orthogonal behaviours. * Natural modeling for cross-cutting capabilities. **Pitfalls:** * Diamond problem (duplicate base subobjects). * Increased complexity — careful design required. --- ### Quick note: Diamond problem If `A` -> `B` and `A` -> `C`, and `D` inherits `B` and `C`, then `A` may appear twice in `D` unless virtual inheritance is used.
Diamond Problem Class Diagram
A
B
C
D
Two paths to A in D!
Ambiguity without virtual inheritance
## Example: Amphibious Vehicle ```cpp #include
using namespace std; class LandVehicle { public: void drive() const { cout << "Driving on road\n"; } }; class WaterVehicle { public: void sail() const { cout << "Sailing on water\n"; } }; class Amphibious : public LandVehicle, public WaterVehicle { public: void operate() const { drive(); sail(); } }; int main() { Amphibious am; am.operate(); return 0; } ```
## Diamond Problem Example (ambiguous without virtual inheritance) ```cpp #include
using namespace std; class Animal { public: void eat() { cout << "Eating..." << endl; } }; class Mammal : public Animal {}; class Aquatic : public Animal {}; class Platypus : public Mammal, public Aquatic {}; int main() { Platypus p; // p.eat(); // Ambiguous! Two 'Animal' copies p.Mammal::eat(); // OK, specify which path p.Aquatic::eat(); // OK, specify which path return 0; } ``` **Error:** `error: request for member 'eat' is ambiguous` **Fix:** Use `virtual public Animal` in `Mammal` and `Aquatic`.
## Quiz: Multiple Inheritance **Q1.** The "Diamond Problem" occurs when: A. A class inherits from two unrelated interfaces B. A derived class inherits the same base subobject through two paths C. The compiler runs out of memory D. Two functions have the same name --- **Answer:** B.
## Public vs Private Inheritance **Public inheritance:** models an "is-a" relationship; public and protected members remain accessible accordingly. **Private inheritance:** models "implemented-in-terms-of"; base's public interface is hidden from outsiders. **Use-case guidance:** * Use public when substitutability is required. * Use private when reusing implementation but hiding base semantics. --- *`using` can selectively re-expose base members when private inheritance is used.*
```cpp #include
using namespace std; class Base { public: void show() { cout << "Base::show()" << endl; } void display() { cout << "Base::display()" << endl; } }; class Derived : private Base { public: using Base::show; // selectively re-expose Base::show() as public void callDisplay() { Base::display(); // accessible only within Derived } }; int main() { Derived d; d.show(); // allowed (re-exposed) // d.display(); // error: 'display' is private in Derived } ```
## Public Inheritance: Zoo example ```cpp #include
#include
using namespace std; class Animal { protected: string species; public: Animal(const string& s) : species(s) {} virtual void speak() const { cout << species << " makes a sound\n"; } virtual ~Animal() = default; }; class Lion : public Animal { public: Lion() : Animal("Lion") {} void speak() const override { cout << "Roar\n"; } }; int main() { Animal* a = new Lion(); a->speak(); // Polymorphic call delete a; return 0; } ``` --- *Demonstrates public inheritance and polymorphism via virtual function.*
## Private Inheritance: Implementing a Stack ```cpp #include
#include
using namespace std; class VectorWrapper { protected: vector
data; void push_back(int x) { data.push_back(x); } void pop_back() { data.pop_back(); } int back() const { return data.back(); } bool empty() const { return data.empty(); } }; class Stack : private VectorWrapper { public: void push(int x) { push_back(x); } int pop() { if (empty()) throw runtime_error("Stack empty"); int v = back(); pop_back(); return v; } size_t size() const { return data.size(); } // re-expose needed method }; int main() { Stack s; s.push(10); s.push(20); cout << s.pop() << "\n"; // 20 cout << s.pop() << "\n"; // 10 return 0; } ``` --- *Vector functions are hidden; Stack exposes only the intended API.*
## Quiz: Inheritance Types **Q2.** Which is correct? A. Private inheritance implies "is-a" relationship B. Public inheritance hides the base class interface C. Private inheritance is suitable when you want to reuseImplementation but not expose base semantics D. Both A and B --- **Answer:** C.
## Virtual Functions and the V-Table **Virtual function:** enables runtime dispatch through a v-table lookup. **Implementation notes:** * A class with virtual functions has a v-table. * Each object typically stores a pointer to its class' v-table. * `override` enforces intent and prevents silent mismatches. ## **Trade-off:** minor runtime cost; large gains in extensibility. *Use virtual destructors when using polymorphism to avoid leaks.*
## Virtual Functions: Safe polymorphism ```cpp #include
#include
using namespace std; class Shape { public: virtual void draw() const { cout << "Generic shape\n"; } virtual ~Shape() = default; }; class Circle : public Shape { public: void draw() const override { cout << "Drawing a circle\n"; } }; class Square : public Shape { public: void draw() const override { cout << "Drawing a square\n"; } }; int main() { vector
shapes; shapes.push_back(new Circle()); shapes.push_back(new Square()); for (auto s : shapes) s->draw(); for (auto s : shapes) delete s; return 0; } ``` --- *Illustrates v-table-powered calls using `override` and a virtual destructor.*
## Quiz: Virtual Functions **Q3.** Why declare destructors `virtual` in base classes? A. To allow compile-time selection B. To ensure derived destructors are invoked when deleting via base pointer C. To avoid type casting D. Not necessary ever --- **Answer:** B.
## Polymorphism: Design and Patterns **Purpose:** Write code operating on base interfaces while letting derived types supply behavior. **Patterns that rely on polymorphism:** * Factory * Strategy * Visitor (advanced) --- *Polymorphism increases extensibility and reduces conditional branching.*
## Polymorphism Example: Factory Pattern ```cpp #include
#include
#include
using namespace std; class Vehicle { public: virtual void drive() const = 0; virtual ~Vehicle() = default; }; class Car : public Vehicle { public: void drive() const override { cout << "Car: Vroom\n"; } }; class Bike : public Vehicle { public: void drive() const override { cout << "Bike: Zoom\n"; } }; Vehicle* createVehicle(const string& type) { if (type == "car") return new Car(); return new Bike(); } int main() { vector
fleet; fleet.push_back(createVehicle("car")); fleet.push_back(createVehicle("bike")); for (auto v : fleet) v->drive(); for (auto v : fleet) delete v; return 0; } ``` --- *Factory returns base pointer while concrete types supply implementations.*
## Quiz: Polymorphism **Q4.** The primary advantage of polymorphism is: A. Faster execution B. Reduced binary size C. Writing generic, extensible code that works with multiple types at runtime D. Eliminating the need for headers --- **Answer:** C.
## Mini Challenge (in-class) Design a polymorphic media player: * Base: `Song` with `virtual void play()`. * Derived: `MP3Song`, `WavSong` — override `play()` with different messages. * Provide `playAll(const vector
&)` that calls `play()` polymorphically. **Deliverable:** Draw class diagram and write a short main() implementing the playlist.
## Hands-on Exercise (homework) 1. Implement a `Zoo` with: * `Animal` (base), `Mammal`, `Bird`, `Aquatic` (intermediate). * `Platypus` inherits from both `Mammal` and `Aquatic`. * Use `virtual` inheritance if necessary to avoid duplicate `Animal` subobjects. * Provide `void info()` virtual method that prints species and capabilities. 2. Submit: * Full compilable code. * Short README explaining whether you used virtual inheritance and why. **Hint:** Try both with and without `virtual` inheritance to observe object size and behaviour.
Comprehensive Zoo Class Inheritance Diagram
Zoo Class Inheritance Hierarchy
Animal
+ name: string
+ age: int
+ makeSound()
Mammal
+ furColor: string
+ giveBirth()
Bird
+ wingspan: float
+ fly()
Reptile
+ scaleType: string
+ shedSkin()
Aquatic
+ depth: int
+ swim()
Flying
+ maxAltitude: int
+ takeOff()
Lion
+ roar()
Elephant
+ trumpet()
Snake
+ slither()
Shark
+ hunt()
Eagle
+ dive()
Bat
+ echolocate()
Platypus
+ layEggs()
Penguin
+ waddle()
Legend
Base Class - Root of hierarchy (Animal)
Intermediate Classes - Shared traits (Mammal, Bird, etc.)
Concrete Classes - Single inheritance (Lion, Elephant, Snake, etc.)
Multiple Inheritance - Inherits from 2+ classes (Bat, Platypus, Penguin)
Note: Multiple inheritance should use virtual inheritance to avoid diamond problem
## Complete Example: Platypus with virtual Inheritance ```cpp #include
#include
using namespace std; class Animal { protected: string name; public: Animal(const string& n="Animal") : name(n) {} virtual void info() const { cout << "Animal: " << name << "\n"; } virtual ~Animal() = default; }; class Mammal : virtual public Animal { public: Mammal(const string& n="Mammal") : Animal(n) {} void nurse() const { cout << "Nursing young\n"; } void info() const override { cout << "Mammal: " << name << "\n"; } }; class Aquatic : virtual public Animal { public: Aquatic(const string& n="Aquatic") : Animal(n) {} void swim() const { cout << "Swimming\n"; } void info() const override { cout << "Aquatic: " << name << "\n"; } }; class Platypus : public Mammal, public Aquatic { public: Platypus() : Animal("Platypus"), Mammal("Platypus"), Aquatic("Platypus") {} void info() const override { cout << "Platypus: a mammal that is semi-aquatic and lays eggs\n"; } }; int main() { Platypus p; p.info(); p.nurse(); p.swim(); return 0; } ``` --- *Demonstrates `virtual` base `Animal` to avoid diamond duplication.*
## Summary * Multiple inheritance is powerful but requires care (diamond problem). * Use virtual inheritance when sharing a common ancestor across multiple paths. * Prefer public inheritance for "is-a" relationships; private when hiding implementation. * Always design with clear ownership and virtual destructors for polymorphism. --- **Next:** Implement the Zoo exercise; test with and without `virtual` inheritance to observe differences.
Navigation
Back to Course Outline
Previous: Friend Functions and Inheritance