Inheritance
Inheritance lets you build a new class (derived) on top of an existing one (base). The derived class inherits all non-private members of the base, and can add new members or override existing behaviour. Inheritance models the "is-a" relationship: a Dog is an Animal, a SavingsAccount is a BankAccount.
Basic Inheritance
#include <iostream>
#include <string>
using namespace std;
// Base class
class Animal {
protected: // Protected — accessible in derived classes
string name;
int age;
public:
Animal(const string& name, int age) : name(name), age(age) {}
void breathe() const {
cout << name << " breathes\n";
}
void eat() const {
cout << name << " eats\n";
}
string getName() const { return name; }
};
// Derived class — inherits Animal publicly
class Dog : public Animal {
string breed;
public:
// Call base constructor via initialiser list
Dog(const string& name, int age, const string& breed)
: Animal(name, age), breed(breed) {}
void bark() const {
cout << name << " barks: Woof!\n";
}
void info() const {
cout << name << " (" << breed << "), age " << age << "\n";
}
};
class Cat : public Animal {
public:
Cat(const string& name, int age) : Animal(name, age) {}
void meow() const {
cout << name << " meows: Meow!\n";
}
};
int main() {
Dog dog("Rex", 3, "German Shepherd");
dog.breathe(); // Inherited from Animal
dog.eat(); // Inherited from Animal
dog.bark(); // Dog-specific
dog.info(); // Dog-specific
Cat cat("Whiskers", 5);
cat.breathe(); // Inherited
cat.meow(); // Cat-specific
return 0;
}
// Rex breathes
// Rex eats
// Rex barks: Woof!
// Rex (German Shepherd), age 3
// Whiskers breathes
// Whiskers meows: Meow!Inheritance Access Specifiers
| Base member | public inheritance | protected inheritance | private inheritance |
|---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | Inaccessible | Inaccessible | Inaccessible |
Use public inheritance for "is-a" relationships. Use private inheritance for "implemented-in-terms-of" (composition via inheritance — rarely needed).
Virtual Functions and Overriding
#include <iostream>
#include <string>
using namespace std;
class Shape {
public:
virtual double area() const { // Virtual — can be overridden
return 0.0;
}
virtual string name() const {
return "Shape";
}
virtual void describe() const { // Non-pure virtual — has default implementation
cout << name() << " with area " << area() << "\n";
}
virtual ~Shape() {} // Virtual destructor — essential for polymorphism!
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { // 'override' — compiler checks base has virtual
return 3.14159265358979 * radius * radius;
}
string name() const override { return "Circle(r=" + to_string(radius) + ")"; }
};
class Rectangle : public Shape {
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override { return width * height; }
string name() const override { return "Rectangle"; }
};
class Triangle : public Shape {
double base, height;
public:
Triangle(double b, double h) : base(b), height(h) {}
double area() const override { return 0.5 * base * height; }
string name() const override { return "Triangle"; }
};
int main() {
Circle c(5.0);
Rectangle r(4.0, 6.0);
Triangle t(3.0, 8.0);
// Polymorphism via base class pointer
Shape* shapes[] = {&c, &r, &t};
for (Shape* s : shapes) {
s->describe();
}
// Circle(r=5.000000) with area 78.5398
// Rectangle with area 24
// Triangle with area 12
return 0;
}Warning: Always declare a virtual destructor in base classes that will be used polymorphically. Without it, deleting a derived object through a base pointer causes undefined behaviour (partial destruction).
Abstract Classes and Pure Virtual Functions
A pure virtual function (declared with = 0) has no implementation in the base class. A class with at least one pure virtual function is abstract — you cannot instantiate it directly:
#include <iostream>
#include <string>
using namespace std;
class Logger {
public:
// Pure virtual — subclasses MUST implement this
virtual void log(const string& message) = 0;
virtual void flush() = 0;
// Non-pure virtual — provides default
virtual void logError(const string& msg) {
log("[ERROR] " + msg);
}
virtual ~Logger() {}
};
class ConsoleLogger : public Logger {
public:
void log(const string& message) override {
cout << message << "\n";
}
void flush() override { /* cout doesn't need flushing manually */ }
};
class FileLogger : public Logger {
string filename;
vector<string> buffer;
public:
FileLogger(const string& fn) : filename(fn) {}
void log(const string& message) override {
buffer.push_back(message);
}
void flush() override {
cout << "Writing " << buffer.size() << " lines to " << filename << "\n";
buffer.clear();
}
};
void process(Logger& logger) {
logger.log("Application started");
logger.logError("Config file missing");
logger.flush();
}
int main() {
// Logger logger; // ERROR — abstract class
ConsoleLogger clog;
FileLogger flog("app.log");
process(clog);
process(flog);
return 0;
}Calling Base Class Functions
#include <iostream>
using namespace std;
class Vehicle {
protected:
string brand;
int speed;
public:
Vehicle(const string& brand, int speed) : brand(brand), speed(speed) {}
virtual void describe() const {
cout << "Vehicle: " << brand << ", max speed " << speed << " km/h\n";
}
};
class Car : public Vehicle {
int doors;
public:
Car(const string& brand, int speed, int doors)
: Vehicle(brand, speed), doors(doors) {}
void describe() const override {
Vehicle::describe(); // Call base version first
cout << " Doors: " << doors << "\n"; // Add derived-specific info
}
};
int main() {
Car car("Toyota", 180, 4);
car.describe();
// Vehicle: Toyota, max speed 180 km/h
// Doors: 4
return 0;
}Multiple Inheritance
C++ allows inheriting from more than one base class:
#include <iostream>
using namespace std;
class Flyable {
public:
virtual void fly() { cout << "Flying\n"; }
virtual ~Flyable() {}
};
class Swimmable {
public:
virtual void swim() { cout << "Swimming\n"; }
virtual ~Swimmable() {}
};
class Duck : public Flyable, public Swimmable {
public:
void fly() override { cout << "Duck flies\n"; }
void swim() override { cout << "Duck swims\n"; }
void quack() { cout << "Quack!\n"; }
};
int main() {
Duck d;
d.fly(); // Duck flies
d.swim(); // Duck swims
d.quack(); // Quack!
Flyable* fb = &d; // Pointer to Flyable interface
Swimmable* sb = &d; // Pointer to Swimmable interface
fb->fly(); // Duck flies (virtual dispatch)
sb->swim(); // Duck swims
return 0;
}Note: Multiple inheritance with data (member variables) in both bases can lead to the diamond problem. Use virtual inheritance (class A : virtual public Base) to resolve it, or redesign using composition.
The final Keyword (C++11)
class Base { virtual void foo(); };
class MidLevel : public Base {
void foo() override final; // Subclasses cannot override foo anymore
};
class Leaf final : public MidLevel {
// No class can inherit from Leaf
};
// class Extended : public Leaf {}; // ERROR — Leaf is finalPractical Example: Payment System
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
class Payment {
protected:
string currency;
double amount;
public:
Payment(double amount, const string& currency = "INR")
: amount(amount), currency(currency) {}
virtual bool process() = 0; // Must implement
virtual string methodName() = 0;
virtual string receipt() const { return ""; }
virtual ~Payment() {}
double getAmount() const { return amount; }
const string& getCurrency() const { return currency; }
};
class UPIPayment : public Payment {
string upiId;
public:
UPIPayment(double amount, const string& upiId)
: Payment(amount), upiId(upiId) {}
bool process() override {
cout << "Processing UPI payment of " << currency << " " << amount
<< " to " << upiId << "\n";
return true; // Simulate success
}
string methodName() override { return "UPI"; }
string receipt() const override {
return "UPI-" + upiId + "-" + to_string(amount);
}
};
class CardPayment : public Payment {
string cardNumber;
string cardHolder;
public:
CardPayment(double amount, const string& card, const string& holder)
: Payment(amount), cardNumber(card), cardHolder(holder) {}
bool process() override {
string masked = "****-****-****-" + cardNumber.substr(cardNumber.size() - 4);
cout << "Processing card payment of " << currency << " " << amount
<< " for " << cardHolder << " (" << masked << ")\n";
return true;
}
string methodName() override { return "Card"; }
};
class PaymentProcessor {
public:
static void processAll(vector<unique_ptr<Payment>>& payments) {
double total = 0;
int success = 0;
for (auto& p : payments) {
if (p->process()) {
total += p->getAmount();
success++;
}
}
cout << "\n--- Summary ---\n";
cout << success << "/" << payments.size() << " payments processed\n";
cout << "Total: INR " << total << "\n";
}
};
int main() {
vector<unique_ptr<Payment>> payments;
payments.push_back(make_unique<UPIPayment>(1500.0, "alice@paytm"));
payments.push_back(make_unique<CardPayment>(3200.0, "4111111111111111", "Bob"));
payments.push_back(make_unique<UPIPayment>(800.0, "charlie@gpay"));
PaymentProcessor::processAll(payments);
// Processing UPI payment of INR 1500 to alice@paytm
// Processing card payment of INR 3200 for Bob (****-****-****-1111)
// Processing UPI payment of INR 800 to charlie@gpay
//
// --- Summary ---
// 3/3 payments processed
// Total: INR 5500
return 0;
}Summary
- Use
publicinheritance for "is-a" relationships; the derived class gets all public and protected members virtualfunctions enable runtime polymorphism — the correct derived function is called through a base pointer- Always declare a virtual destructor in base classes used polymorphically — prevents partial destruction bugs
- Pure virtual functions (
= 0) make a class abstract — it cannot be instantiated, only subclassed - Use
overrideon every overriding function — the compiler verifies the base class actually has a matching virtual - Use
finalon a class or method to prevent further inheritance or overriding - Multiple inheritance works but can lead to diamond problems — prefer composition or virtual inheritance
Next, we'll explore polymorphism — the mechanism behind virtual function dispatch and the vtable.