Introduction to Inheritance
Inheritance is one of the four pillars of Object-Oriented Programming. It allows you to create new classes based on existing ones, inheriting their properties and behaviors while adding new features or modifying existing ones. This promotes code reuse and establishes natural hierarchies.
Inheritance
Inheritance is a mechanism where a new class (derived/child class) acquires the properties and behaviors of an existing class (base/parent class). The derived class can add new members or modify inherited behavior while maintaining an "is-a" relationship with the base class.
Key Relationship: A Dog "is-a" Animal, a Car "is-a" Vehicle, a Student "is-a" Person.
// Without inheritance - code duplication
class Dog {
public:
string name;
int age;
void eat() { cout << name << " is eating" << endl; }
void sleep() { cout << name << " is sleeping" << endl; }
void bark() { cout << name << " says Woof!" << endl; }
};
class Cat {
public:
string name;
int age;
void eat() { cout << name << " is eating" << endl; } // Duplicated!
void sleep() { cout << name << " is sleeping" << endl; } // Duplicated!
void meow() { cout << name << " says Meow!" << endl; }
};
Without inheritance, common attributes like name and age, and common
behaviors like eat() and sleep(), must be duplicated in every related
class. This violates the DRY (Don't Repeat Yourself) principle and makes maintenance difficult -
if you need to change how eating works, you must update every class.
// With inheritance - shared code in base class
class Animal {
public:
string name;
int age;
void eat() { cout << name << " is eating" << endl; }
void sleep() { cout << name << " is sleeping" << endl; }
};
class Dog : public Animal {
public:
void bark() { cout << name << " says Woof!" << endl; }
};
class Cat : public Animal {
public:
void meow() { cout << name << " says Meow!" << endl; }
};
With inheritance, Dog and Cat inherit from Animal. They
automatically get name, age, eat(), and sleep().
Each derived class only adds what makes it unique - bark() for Dog and meow()
for Cat. Changes to shared behavior are made once in Animal.
int main() {
Dog buddy;
buddy.name = "Buddy"; // Inherited from Animal
buddy.age = 3; // Inherited from Animal
buddy.eat(); // Inherited: "Buddy is eating"
buddy.sleep(); // Inherited: "Buddy is sleeping"
buddy.bark(); // Dog-specific: "Buddy says Woof!"
Cat whiskers;
whiskers.name = "Whiskers";
whiskers.eat(); // "Whiskers is eating"
whiskers.meow(); // "Whiskers says Meow!"
return 0;
}
Objects of derived classes can use both inherited members and their own specific members. The syntax is identical - the user of the class does not need to know which members are inherited and which are defined in the derived class.
Benefits of Inheritance
- Code reusability - write once, use many times
- Establishes natural relationships
- Easier maintenance and updates
- Enables polymorphism
When NOT to Use
- No clear "is-a" relationship
- Just for code sharing (use composition)
- Deep hierarchies (more than 3 levels)
- Unrelated classes
Base and Derived Classes
In inheritance, the existing class is called the base class (or parent/superclass), and the new class that inherits from it is called the derived class (or child/subclass). The derived class inherits all non-private members from the base class and can extend or specialize its behavior.
Base and Derived Classes
The base class (parent) is the class being inherited from. The derived
class (child) is the class that inherits. Syntax: class Derived : access_specifier Base
Syntax: class DerivedClass : public BaseClass { /* members */ };
// Base class (parent)
class Vehicle {
public:
string brand;
int year;
void start() {
cout << brand << " is starting..." << endl;
}
void stop() {
cout << brand << " is stopping..." << endl;
}
};
The Vehicle class is our base class with common attributes (brand,
year) and behaviors (start(), stop()) that all vehicles share.
This becomes the foundation that specific vehicle types will build upon.
// Derived class (child) - inherits from Vehicle
class Car : public Vehicle {
public:
int numDoors;
void honk() {
cout << brand << " goes Beep Beep!" << endl;
}
};
The Car class inherits from Vehicle using : public Vehicle.
It automatically has brand, year, start(), and stop().
It adds its own member numDoors and method honk().
// Another derived class
class Motorcycle : public Vehicle {
public:
bool hasSidecar;
void wheelie() {
cout << brand << " is doing a wheelie!" << endl;
}
};
Motorcycle also inherits from Vehicle but adds different specialized members.
Both Car and Motorcycle share the base class interface but have their own
unique extensions.
int main() {
Car myCar;
myCar.brand = "Toyota"; // Inherited
myCar.year = 2023; // Inherited
myCar.numDoors = 4; // Car-specific
myCar.start(); // Inherited: "Toyota is starting..."
myCar.honk(); // Car-specific: "Toyota goes Beep Beep!"
myCar.stop(); // Inherited: "Toyota is stopping..."
Motorcycle myBike;
myBike.brand = "Harley";
myBike.hasSidecar = false;
myBike.start(); // "Harley is starting..."
myBike.wheelie(); // "Harley is doing a wheelie!"
return 0;
}
Each derived class object has all the members - both inherited and its own. You can use them
interchangeably. Notice how honk() uses the inherited brand member directly -
inherited members are fully accessible in the derived class.
| Term | Also Known As | Description |
|---|---|---|
| Base Class | Parent, Superclass | The class being inherited from |
| Derived Class | Child, Subclass | The class that inherits |
| Inheritance | Extension, Derivation | The mechanism of acquiring members |
| Member | Property/Method | Data or function in a class |
Practice Questions: Base and Derived Classes
Task: Create a Person class with name and age. Create a Student class that inherits from Person and adds studentId and a study() method.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string name;
int age;
void introduce() {
cout << "Hi, I'm " << name << ", " << age << " years old." << endl;
}
};
class Student : public Person {
public:
string studentId;
void study() {
cout << name << " is studying..." << endl;
}
};
int main() {
Student s;
s.name = "Alice";
s.age = 20;
s.studentId = "STU001";
s.introduce(); // "Hi, I'm Alice, 20 years old."
s.study(); // "Alice is studying..."
return 0;
}
Task: Create a Shape class with color. Create a Rectangle class that inherits from Shape and adds width, height, and an area() method.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Shape {
public:
string color;
void displayColor() {
cout << "Color: " << color << endl;
}
};
class Rectangle : public Shape {
public:
double width;
double height;
double area() {
return width * height;
}
};
int main() {
Rectangle rect;
rect.color = "Blue";
rect.width = 10;
rect.height = 5;
rect.displayColor(); // "Color: Blue"
cout << "Area: " << rect.area() << endl; // "Area: 50"
return 0;
}
Task: Create an Employee class with name, salary, and work(). Create a Manager class that adds teamSize and conductMeeting().
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Employee {
public:
string name;
double salary;
void work() {
cout << name << " is working..." << endl;
}
void displayInfo() {
cout << "Name: " << name << ", Salary: $" << salary << endl;
}
};
class Manager : public Employee {
public:
int teamSize;
void conductMeeting() {
cout << name << " is conducting a meeting with "
<< teamSize << " team members." << endl;
}
};
int main() {
Manager mgr;
mgr.name = "John";
mgr.salary = 75000;
mgr.teamSize = 8;
mgr.displayInfo(); // "Name: John, Salary: $75000"
mgr.work(); // "John is working..."
mgr.conductMeeting(); // "John is conducting a meeting with 8 team members."
return 0;
}
Task: Create a BankAccount class with balance, deposit(), and withdraw(). Create a SavingsAccount that adds interestRate and addInterest().
Show Solution
#include <iostream>
using namespace std;
class BankAccount {
public:
double balance;
BankAccount() : balance(0) {}
void deposit(double amount) {
if (amount > 0) {
balance += amount;
cout << "Deposited: $" << amount << endl;
}
}
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
cout << "Withdrawn: $" << amount << endl;
return true;
}
cout << "Insufficient funds!" << endl;
return false;
}
void showBalance() {
cout << "Balance: $" << balance << endl;
}
};
class SavingsAccount : public BankAccount {
public:
double interestRate;
SavingsAccount() : interestRate(0.05) {}
void addInterest() {
double interest = balance * interestRate;
balance += interest;
cout << "Interest added: $" << interest << endl;
}
};
int main() {
SavingsAccount savings;
savings.deposit(1000);
savings.showBalance(); // $1000
savings.addInterest(); // Interest added: $50
savings.showBalance(); // $1050
savings.withdraw(200);
savings.showBalance(); // $850
return 0;
}
Access Specifiers in Inheritance
When inheriting from a base class, you specify an access specifier (public, protected, or private) that controls how inherited members are accessible in the derived class. This, combined with the original access level in the base class, determines the final accessibility of inherited members.
Access Specifiers
The inheritance access specifier (public, protected, private)
determines the maximum accessibility of inherited members. Public inheritance is most common and
maintains the original access levels.
Rule: protected members in base class are accessible in derived classes but not from outside.
class Base {
public:
int publicVar; // Accessible everywhere
protected:
int protectedVar; // Accessible in Base and derived classes
private:
int privateVar; // Only accessible in Base
};
The base class has three different access levels. public members can be accessed from
anywhere. protected members are like private but accessible in derived classes.
private members are strictly limited to the base class itself.
// Public inheritance (most common)
class PublicDerived : public Base {
public:
void access() {
publicVar = 1; // OK - remains public
protectedVar = 2; // OK - remains protected
// privateVar = 3; // ERROR - private is never inherited
}
};
With public inheritance, public members stay public and protected members stay protected.
Private members are never directly accessible in derived classes - they exist but are hidden. This is
the most common inheritance mode as it preserves the base class interface.
// Protected inheritance
class ProtectedDerived : protected Base {
public:
void access() {
publicVar = 1; // OK - becomes protected
protectedVar = 2; // OK - remains protected
}
};
// Private inheritance
class PrivateDerived : private Base {
public:
void access() {
publicVar = 1; // OK - becomes private
protectedVar = 2; // OK - becomes private
}
};
With protected inheritance, all inherited members become at most protected. With
private inheritance, all inherited members become private - useful when you want to
use a base class for implementation but not expose its interface.
int main() {
PublicDerived pub;
pub.publicVar = 10; // OK - public remains public
// pub.protectedVar = 20; // ERROR - protected not accessible outside
ProtectedDerived prot;
// prot.publicVar = 10; // ERROR - became protected
PrivateDerived priv;
// priv.publicVar = 10; // ERROR - became private
return 0;
}
From outside the class, only public members are accessible. Protected inheritance hides the base class interface from external code but allows further derivation. Private inheritance completely hides the relationship.
| Base Member | public Inheritance | protected Inheritance | private Inheritance |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | not accessible | not accessible | not accessible |
public inheritance for "is-a" relationships (a Dog is an Animal).
Use private inheritance for "is-implemented-in-terms-of" relationships (implementation detail).
Practice Questions: Access Specifiers
Task: Create an Account class with protected balance. Create a CheckingAccount that can access and modify the balance through methods.
Show Solution
#include <iostream>
using namespace std;
class Account {
protected:
double balance;
public:
Account() : balance(0) {}
double getBalance() { return balance; }
};
class CheckingAccount : public Account {
public:
void deposit(double amount) {
if (amount > 0) {
balance += amount; // Can access protected member
cout << "Deposited: $" << amount << endl;
}
}
void withdraw(double amount) {
if (amount <= balance) {
balance -= amount; // Can access protected member
cout << "Withdrawn: $" << amount << endl;
}
}
};
int main() {
CheckingAccount acc;
acc.deposit(500);
acc.withdraw(200);
cout << "Balance: $" << acc.getBalance() << endl; // $300
// acc.balance = 1000; // ERROR - protected not accessible here
return 0;
}
Task: Create a Device class with public name. Create two derived classes: one using public inheritance and one using protected. Show what is accessible from main().
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Device {
public:
string name;
void turnOn() { cout << name << " is ON" << endl; }
};
class Phone : public Device {
public:
void call() { cout << "Calling from " << name << endl; }
};
class Tablet : protected Device {
public:
void setName(string n) { name = n; } // Can access in derived
void browse() { cout << "Browsing on " << name << endl; }
};
int main() {
Phone phone;
phone.name = "iPhone"; // OK - public inheritance
phone.turnOn(); // OK - "iPhone is ON"
phone.call();
Tablet tablet;
// tablet.name = "iPad"; // ERROR - became protected
// tablet.turnOn(); // ERROR - became protected
tablet.setName("iPad"); // OK - through public method
tablet.browse(); // OK - "Browsing on iPad"
return 0;
}
Task: Create a Vehicle class with public, protected, and private members. Create a Car derived class and demonstrate what can be accessed.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Vehicle {
public:
string brand;
protected:
int year;
private:
string vin; // Vehicle Identification Number
public:
void setVin(string v) { vin = v; }
string getVin() { return vin; }
};
class Car : public Vehicle {
public:
int doors;
void setYear(int y) { year = y; } // OK - protected accessible
int getYear() { return year; }
void display() {
cout << brand << " (" << year << ") - " << doors << " doors" << endl;
// cout << vin; // ERROR - private not accessible
cout << "VIN: " << getVin() << endl; // OK - through public method
}
};
int main() {
Car car;
car.brand = "Toyota"; // OK - public
car.doors = 4;
// car.year = 2023; // ERROR - protected
car.setYear(2023); // OK - through method
car.setVin("ABC123");
car.display();
return 0;
}
Task: Create a three-level hierarchy: LivingThing -> Animal -> Dog. Use protected members and show they are accessible at all levels.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class LivingThing {
protected:
bool isAlive;
public:
LivingThing() : isAlive(true) {}
bool alive() { return isAlive; }
};
class Animal : public LivingThing {
protected:
string species;
public:
void setSpecies(string s) {
species = s;
isAlive = true; // Can access LivingThing's protected
}
};
class Dog : public Animal {
private:
string name;
public:
Dog(string n) : name(n) {
species = "Canine"; // Access Animal's protected
isAlive = true; // Access LivingThing's protected
}
void bark() {
if (isAlive) { // Access through all levels
cout << name << " the " << species << " barks!" << endl;
}
}
};
int main() {
Dog buddy("Buddy");
buddy.bark(); // "Buddy the Canine barks!"
cout << "Is alive: " << buddy.alive() << endl; // 1 (true)
return 0;
}
Constructor Chaining
When a derived class object is created, the base class constructor must be called first to initialize inherited members. This is called constructor chaining. C++ allows you to explicitly specify which base class constructor to call using the member initializer list.
Constructor Chaining
Constructor chaining is the process where a derived class constructor calls its base class constructor before executing its own body. The order is always: base class first, then derived class. Destructors are called in reverse order.
Syntax: Derived(params) : Base(args), member(value) { }
class Animal {
protected:
string name;
int age;
public:
Animal() {
name = "Unknown";
age = 0;
cout << "Animal default constructor" << endl;
}
Animal(string n, int a) : name(n), age(a) {
cout << "Animal parameterized constructor: " << name << endl;
}
};
The Animal base class has two constructors: a default one and a parameterized one.
Both print a message so we can see when they are called. The parameterized constructor uses an
initializer list to set the members.
class Dog : public Animal {
private:
string breed;
public:
// Calls Animal's default constructor automatically
Dog() {
breed = "Mixed";
cout << "Dog default constructor" << endl;
}
// Explicitly calls Animal's parameterized constructor
Dog(string n, int a, string b) : Animal(n, a), breed(b) {
cout << "Dog parameterized constructor: " << breed << endl;
}
};
The Dog class shows two approaches. The default constructor does not specify a base
constructor, so Animal() is called automatically. The parameterized constructor explicitly
calls Animal(n, a) using the initializer list syntax.
int main() {
cout << "Creating dog1:" << endl;
Dog dog1;
// Output:
// Animal default constructor
// Dog default constructor
cout << "\nCreating dog2:" << endl;
Dog dog2("Buddy", 3, "Labrador");
// Output:
// Animal parameterized constructor: Buddy
// Dog parameterized constructor: Labrador
return 0;
}
The output clearly shows the order: base class constructor runs first, then derived class constructor. This ensures that inherited members are properly initialized before the derived class tries to use them.
class Animal {
public:
Animal(string n) { cout << "Animal: " << n << endl; }
~Animal() { cout << "Animal destructor" << endl; }
};
class Dog : public Animal {
public:
Dog(string n) : Animal(n) { cout << "Dog: " << n << endl; }
~Dog() { cout << "Dog destructor" << endl; }
};
int main() {
Dog buddy("Buddy");
return 0;
}
// Output:
// Animal: Buddy
// Dog: Buddy
// Dog destructor
// Animal destructor
Destructors are called in reverse order: derived class destructor first, then base class destructor. This ensures proper cleanup - the derived class cleans up its resources before the base class cleans up the inherited resources.
Practice Questions: Constructor Chaining
Task: Create Person with constructor for name. Create Employee that chains to Person's constructor and adds employeeId.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Person {
protected:
string name;
public:
Person(string n) : name(n) {
cout << "Person created: " << name << endl;
}
};
class Employee : public Person {
private:
int employeeId;
public:
Employee(string n, int id) : Person(n), employeeId(id) {
cout << "Employee created: ID " << employeeId << endl;
}
void display() {
cout << name << " (ID: " << employeeId << ")" << endl;
}
};
int main() {
Employee emp("Alice", 1001);
emp.display();
return 0;
}
// Output:
// Person created: Alice
// Employee created: ID 1001
// Alice (ID: 1001)
Task: Create Vehicle with brand and year constructor. Create Car that chains to Vehicle and adds numDoors.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Vehicle {
protected:
string brand;
int year;
public:
Vehicle(string b, int y) : brand(b), year(y) {
cout << "Vehicle: " << brand << " " << year << endl;
}
};
class Car : public Vehicle {
private:
int numDoors;
public:
Car(string b, int y, int doors) : Vehicle(b, y), numDoors(doors) {
cout << "Car: " << numDoors << " doors" << endl;
}
void display() {
cout << year << " " << brand << " (" << numDoors << " doors)" << endl;
}
};
int main() {
Car myCar("Toyota", 2023, 4);
myCar.display();
return 0;
}
// Output:
// Vehicle: Toyota 2023
// Car: 4 doors
// 2023 Toyota (4 doors)
Task: Create a three-level hierarchy A -> B -> C. Add print statements in constructors and destructors to show the order.
Show Solution
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A constructor" << endl; }
~A() { cout << "A destructor" << endl; }
};
class B : public A {
public:
B() { cout << "B constructor" << endl; }
~B() { cout << "B destructor" << endl; }
};
class C : public B {
public:
C() { cout << "C constructor" << endl; }
~C() { cout << "C destructor" << endl; }
};
int main() {
cout << "Creating C object:" << endl;
C obj;
cout << "\nDestroying C object:" << endl;
return 0;
}
// Output:
// Creating C object:
// A constructor
// B constructor
// C constructor
//
// Destroying C object:
// C destructor
// B destructor
// A destructor
Task: Create GameObject with x, y coordinates. Create Character that adds health and name. Create Player that adds score. Chain all constructors properly.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class GameObject {
protected:
int x, y;
public:
GameObject(int px, int py) : x(px), y(py) {
cout << "GameObject at (" << x << ", " << y << ")" << endl;
}
};
class Character : public GameObject {
protected:
string name;
int health;
public:
Character(int px, int py, string n, int h)
: GameObject(px, py), name(n), health(h) {
cout << "Character: " << name << " with " << health << " HP" << endl;
}
};
class Player : public Character {
private:
int score;
public:
Player(int px, int py, string n, int h, int s)
: Character(px, py, n, h), score(s) {
cout << "Player: " << name << " score " << score << endl;
}
void display() {
cout << name << " at (" << x << "," << y << ") HP:" << health
<< " Score:" << score << endl;
}
};
int main() {
Player hero(100, 200, "Hero", 100, 0);
hero.display();
return 0;
}
// Output:
// GameObject at (100, 200)
// Character: Hero with 100 HP
// Player: Hero score 0
// Hero at (100,200) HP:100 Score:0
Method Overriding
Method overriding allows a derived class to provide a specific implementation for a method that is already defined in its base class. The derived class method "overrides" the base class version, providing specialized behavior while maintaining the same interface.
Method Overriding
Method overriding occurs when a derived class defines a method with the same name, return type, and parameters as a method in the base class. The derived version replaces the base version for objects of the derived type.
Note: Overriding is different from overloading. Overloading = same name, different parameters. Overriding = same signature, different class.
class Animal {
public:
void speak() {
cout << "Animal makes a sound" << endl;
}
void eat() {
cout << "Animal is eating" << endl;
}
};
The base Animal class defines generic behaviors. The speak() method
produces a generic message. Derived classes will want to customize this for their specific sounds.
class Dog : public Animal {
public:
// Override speak() - same name, same parameters
void speak() {
cout << "Dog says Woof!" << endl;
}
// eat() is inherited as-is
};
class Cat : public Animal {
public:
void speak() {
cout << "Cat says Meow!" << endl;
}
};
Both Dog and Cat override speak() with their own implementations.
The eat() method is not overridden, so it remains the inherited version. Each class can
choose which methods to override.
int main() {
Dog dog;
dog.speak(); // "Dog says Woof!" - overridden
dog.eat(); // "Animal is eating" - inherited
Cat cat;
cat.speak(); // "Cat says Meow!" - overridden
cat.eat(); // "Animal is eating" - inherited
Animal animal;
animal.speak(); // "Animal makes a sound" - base version
return 0;
}
When you call speak() on a Dog object, you get the Dog version. When called on an
Animal object, you get the Animal version. The actual type of the object determines which version runs.
class Dog : public Animal {
public:
void speak() {
// Call base class version first
Animal::speak();
// Then add derived behavior
cout << "Dog says Woof!" << endl;
}
};
You can call the base class method from the overriding method using the scope resolution operator
(Animal::speak()). This is useful when you want to extend rather than completely
replace the base behavior.
int main() {
Dog dog;
dog.speak();
// Output:
// Animal makes a sound
// Dog says Woof!
return 0;
}
By calling Animal::speak() first, the Dog's speak() runs both the base
class behavior and its own additional behavior. This pattern is common when extending functionality
rather than replacing it entirely.
virtual functions -
covered in the next topic on Polymorphism.
Practice Questions: Method Overriding
Task: Create a Shape class with display() that prints "Shape". Create Circle that overrides it to print "Circle".
Show Solution
#include <iostream>
using namespace std;
class Shape {
public:
void display() {
cout << "This is a Shape" << endl;
}
};
class Circle : public Shape {
public:
void display() {
cout << "This is a Circle" << endl;
}
};
int main() {
Shape s;
s.display(); // "This is a Shape"
Circle c;
c.display(); // "This is a Circle"
return 0;
}
Task: Create Employee with calculatePay() returning base salary. Create Manager that overrides it to add a bonus.
Show Solution
#include <iostream>
using namespace std;
class Employee {
protected:
double baseSalary;
public:
Employee(double salary) : baseSalary(salary) {}
double calculatePay() {
return baseSalary;
}
};
class Manager : public Employee {
private:
double bonus;
public:
Manager(double salary, double b) : Employee(salary), bonus(b) {}
double calculatePay() {
return baseSalary + bonus; // Override to include bonus
}
};
int main() {
Employee emp(50000);
cout << "Employee pay: $" << emp.calculatePay() << endl; // $50000
Manager mgr(50000, 10000);
cout << "Manager pay: $" << mgr.calculatePay() << endl; // $60000
return 0;
}
Task: Create Logger with log() that prints timestamp. Create FileLogger that calls base log() then adds file writing message.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Logger {
public:
void log(string message) {
cout << "[TIMESTAMP] " << message << endl;
}
};
class FileLogger : public Logger {
private:
string filename;
public:
FileLogger(string file) : filename(file) {}
void log(string message) {
Logger::log(message); // Call base class first
cout << "[Writing to " << filename << "]" << endl;
}
};
int main() {
Logger basic;
basic.log("Hello");
// [TIMESTAMP] Hello
cout << endl;
FileLogger fileLog("app.log");
fileLog.log("Error occurred");
// [TIMESTAMP] Error occurred
// [Writing to app.log]
return 0;
}
Task: Create Character with attack() dealing base damage. Create Warrior (2x damage) and Mage (1.5x damage + magic effect). Each override should call base first.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Character {
protected:
string name;
int baseDamage;
public:
Character(string n, int dmg) : name(n), baseDamage(dmg) {}
int attack() {
cout << name << " attacks for " << baseDamage << " damage!" << endl;
return baseDamage;
}
};
class Warrior : public Character {
public:
Warrior(string n) : Character(n, 20) {}
int attack() {
Character::attack(); // Base attack
int totalDamage = baseDamage * 2;
cout << name << " uses Power Strike for " << totalDamage << " total damage!" << endl;
return totalDamage;
}
};
class Mage : public Character {
public:
Mage(string n) : Character(n, 15) {}
int attack() {
Character::attack(); // Base attack
int totalDamage = baseDamage * 1.5;
cout << name << " casts Fireball for " << totalDamage << " total damage!" << endl;
cout << "Enemy is now burning!" << endl;
return totalDamage;
}
};
int main() {
Warrior w("Conan");
w.attack();
cout << endl;
Mage m("Gandalf");
m.attack();
return 0;
}
Types of Inheritance
C++ supports multiple types of inheritance patterns, allowing you to create various class hierarchies. Understanding these patterns helps you design flexible and maintainable systems while avoiding common pitfalls like the diamond problem.
Inheritance Types
C++ supports single (one base), multilevel (chain of inheritance), multiple (multiple bases), hierarchical (multiple derived from one base), and hybrid inheritance patterns.
Caution: Multiple inheritance can lead to the "diamond problem" - use virtual inheritance to resolve it.
Single Inheritance
// Single Inheritance: One base, one derived
class Animal {
public:
void eat() { cout << "Eating..." << endl; }
};
class Dog : public Animal {
public:
void bark() { cout << "Barking..." << endl; }
};
// Dog inherits from Animal only
Single inheritance is the simplest form - one derived class inherits from one base class. This creates a simple parent-child relationship. Most inheritance scenarios use single inheritance.
Multilevel Inheritance
// Multilevel Inheritance: Chain of inheritance
class Animal {
public:
void eat() { cout << "Eating..." << endl; }
};
class Mammal : public Animal {
public:
void breathe() { cout << "Breathing..." << endl; }
};
class Dog : public Mammal {
public:
void bark() { cout << "Barking..." << endl; }
};
// Dog -> Mammal -> Animal (three levels)
Multilevel inheritance creates a chain: Dog inherits from Mammal, which inherits from Animal. Dog has access to members from both Mammal and Animal. Each level adds more specialization.
int main() {
Dog dog;
dog.eat(); // From Animal (grandparent)
dog.breathe(); // From Mammal (parent)
dog.bark(); // From Dog (self)
return 0;
}
Objects of the most derived class can use methods from all levels of the hierarchy. The Dog
object has eat() from Animal, breathe() from Mammal, and bark()
defined in Dog.
Multiple Inheritance
// Multiple Inheritance: Multiple base classes
class Flyable {
public:
void fly() { cout << "Flying..." << endl; }
};
class Swimmable {
public:
void swim() { cout << "Swimming..." << endl; }
};
class Duck : public Flyable, public Swimmable {
public:
void quack() { cout << "Quacking..." << endl; }
};
// Duck inherits from both Flyable and Swimmable
Multiple inheritance allows a class to inherit from multiple base classes. The derived class gets members from all bases. Use commas to list multiple base classes.
int main() {
Duck duck;
duck.fly(); // From Flyable
duck.swim(); // From Swimmable
duck.quack(); // From Duck
return 0;
}
The Duck class combines capabilities from both Flyable and Swimmable. This is powerful but use it carefully - it can lead to complexity and the diamond problem.
The Diamond Problem
// Diamond Problem
class Animal {
public:
int age;
};
class Mammal : public Animal { };
class Bird : public Animal { };
class Bat : public Mammal, public Bird { };
// Bat has TWO copies of Animal::age - ambiguous!
The diamond problem occurs when a class inherits from two classes that share a common base. The derived class ends up with two copies of the base class members, causing ambiguity.
// Solution: Virtual Inheritance
class Animal {
public:
int age;
};
class Mammal : virtual public Animal { };
class Bird : virtual public Animal { };
class Bat : public Mammal, public Bird { };
// Now Bat has only ONE copy of Animal::age
Virtual inheritance solves the diamond problem. When you use virtual public, the
most derived class gets only one copy of the virtually inherited base class.
Hierarchical
Multiple classes derive from a single base: Dog, Cat, Bird all inherit from Animal. Creates a "fan-out" pattern.
Hybrid
Combination of multiple inheritance types. Requires careful design to avoid complexity and diamond problems.
Practice Questions: Types of Inheritance
Task: Create a three-level hierarchy: Vehicle -> Car -> SportsCar. Each level should add a unique method.
Show Solution
#include <iostream>
using namespace std;
class Vehicle {
public:
void start() { cout << "Vehicle starting..." << endl; }
};
class Car : public Vehicle {
public:
void drive() { cout << "Car driving..." << endl; }
};
class SportsCar : public Car {
public:
void turboBoost() { cout << "Turbo boost activated!" << endl; }
};
int main() {
SportsCar ferrari;
ferrari.start(); // From Vehicle
ferrari.drive(); // From Car
ferrari.turboBoost(); // From SportsCar
return 0;
}
Task: Create Printable (with print()) and Saveable (with save()). Create Document that inherits from both and has a name.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Printable {
public:
void print() { cout << "Printing document..." << endl; }
};
class Saveable {
public:
void save() { cout << "Saving document..." << endl; }
};
class Document : public Printable, public Saveable {
private:
string name;
public:
Document(string n) : name(n) {}
void display() {
cout << "Document: " << name << endl;
}
};
int main() {
Document doc("Report.docx");
doc.display();
doc.print(); // From Printable
doc.save(); // From Saveable
return 0;
}
Task: Create Shape base class with getArea(). Create three derived classes: Circle, Rectangle, and Triangle, each implementing their own area calculation.
Show Solution
#include <iostream>
using namespace std;
class Shape {
public:
double getArea() { return 0; } // Default
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double getArea() { return 3.14159 * radius * radius; }
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double getArea() { return width * height; }
};
class Triangle : public Shape {
private:
double base, height;
public:
Triangle(double b, double h) : base(b), height(h) {}
double getArea() { return 0.5 * base * height; }
};
int main() {
Circle c(5);
Rectangle r(4, 6);
Triangle t(3, 8);
cout << "Circle area: " << c.getArea() << endl; // 78.5398
cout << "Rectangle area: " << r.getArea() << endl; // 24
cout << "Triangle area: " << t.getArea() << endl; // 12
return 0;
}
Task: Create a diamond: Person -> Student and Employee -> StudentEmployee. Use virtual inheritance and show that StudentEmployee has only one name.
Show Solution
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
string name;
Person() { cout << "Person constructor" << endl; }
};
class Student : virtual public Person {
public:
string major;
Student() { cout << "Student constructor" << endl; }
};
class Employee : virtual public Person {
public:
double salary;
Employee() { cout << "Employee constructor" << endl; }
};
class StudentEmployee : public Student, public Employee {
public:
StudentEmployee() {
cout << "StudentEmployee constructor" << endl;
}
void display() {
// Only ONE name - no ambiguity!
cout << "Name: " << name << endl;
cout << "Major: " << major << endl;
cout << "Salary: $" << salary << endl;
}
};
int main() {
StudentEmployee se;
se.name = "Alice"; // Unambiguous - only one copy
se.major = "CS";
se.salary = 25000;
se.display();
return 0;
}
// Output shows Person constructor called only ONCE
Key Takeaways
Inheritance Enables Reuse
Derived classes inherit members from base classes, promoting code reuse and establishing "is-a" relationships.
Protected for Inheritance
Use protected members when derived classes need access but external code should not. Private members are never inherited.
Chain Constructors Properly
Base class constructors run first. Use initializer lists to call specific base constructors.
Override to Specialize
Derived classes can override base methods to provide specialized behavior. Use BaseClass::method() to call parent version.
Multiple Types Exist
C++ supports single, multilevel, multiple, and hierarchical inheritance. Choose based on your design needs.
Virtual Solves Diamond
Use virtual inheritance when multiple inheritance creates a diamond pattern to avoid duplicate base class members.
Knowledge Check
Quick Quiz
Test what you've learned about C++ Inheritance