Skip to content

Inheritance Basics โ€‹

Video: What is INHERITANCE in C++? ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง

Inheritance is a fundamental concept in object-oriented programming that allows you to create a hierarchy of related classes. It enables code reuse by allowing derived classes to inherit properties and behavior from base classes, while also providing the foundation for polymorphism.

What is Inheritance? โ€‹

Inheritance creates an "is-a" relationship between classes, where a derived class (subclass) inherits from a base class (superclass). The derived class automatically gets all the public and protected members of the base class, and can add its own unique features.

Key Concepts: โ€‹

  • Base Class (Parent): The class being inherited from
  • Derived Class (Child): The class that inherits from the base class
  • Code Reuse: Derived classes can use base class functionality without rewriting
  • Specialization: Derived classes can add or modify behavior
  • Polymorphism: Derived classes can be used where base classes are expected

Basic Inheritance Syntax โ€‹

Simple Inheritance โ€‹

cpp
// Base class
class Animal {
protected:
    std::string name;
    int age;

public:
    Animal(const std::string& n, int a) : name(n), age(a) {}

    void eat() {
        std::cout << name << " is eating." << std::endl;
    }

    void sleep() {
        std::cout << name << " is sleeping." << std::endl;
    }

    virtual void makeSound() {
        std::cout << name << " makes a sound." << std::endl;
    }
};

// Derived class
class Dog : public Animal {
private:
    std::string breed;

public:
    Dog(const std::string& n, int a, const std::string& b)
        : Animal(n, a), breed(b) {}  // Call base class constructor

    void fetch() {
        std::cout << name << " is fetching the ball." << std::endl;
    }

    void makeSound() override {
        std::cout << name << " barks: Woof!" << std::endl;
    }

    void showBreed() {
        std::cout << name << " is a " << breed << "." << std::endl;
    }
};

Key Points:

  • Syntax: class DerivedClass : public BaseClass
  • Constructor Call: : Animal(n, a) in the initialization list
  • Member Access: Derived class can access protected members of base class
  • Method Override: makeSound() is overridden in the derived class

Access Specifiers in Inheritance โ€‹

The access specifier in inheritance determines how base class members are accessible in the derived class:

1. Public Inheritance (Most Common) โ€‹

cpp
class Vehicle {
public:
    std::string brand;
    int year;

protected:
    double fuelLevel;

private:
    std::string vin;
};

class Car : public Vehicle {
public:
    void refuel(double amount) {
        fuelLevel += amount;  // OK: protected member accessible
        brand = "Toyota";     // OK: public member accessible
        // vin = "123";       // ERROR: private member not accessible
    }
};

// Usage
Car myCar;
myCar.brand = "Honda";       // OK: public member accessible from outside
myCar.year = 2023;           // OK: public member accessible from outside
// myCar.fuelLevel = 50.0;   // ERROR: protected member not accessible from outside

Public Inheritance Behavior:

  • Public members remain public in derived class
  • Protected members remain protected in derived class
  • Private members are not accessible in derived class

2. Protected Inheritance โ€‹

cpp
class Vehicle {
public:
    std::string brand;
    int year;

protected:
    double fuelLevel;

private:
    std::string vin;
};

class Car : protected Vehicle {
public:
    void refuel(double amount) {
        fuelLevel += amount;  // OK: protected member accessible
        brand = "Toyota";     // OK: public member now protected
        year = 2023;          // OK: public member now protected
    }
};

// Usage
Car myCar;
// myCar.brand = "Honda";    // ERROR: public member now protected
// myCar.year = 2023;        // ERROR: public member now protected
// myCar.fuelLevel = 50.0;   // ERROR: protected member not accessible from outside

Protected Inheritance Behavior:

  • Public members become protected in derived class
  • Protected members remain protected in derived class
  • Private members are not accessible in derived class

3. Private Inheritance โ€‹

cpp
class Vehicle {
public:
    std::string brand;
    int year;

protected:
    double fuelLevel;

private:
    std::string vin;
};

class Car : private Vehicle {
public:
    void refuel(double amount) {
        fuelLevel += amount;  // OK: protected member now private
        brand = "Toyota";     // OK: public member now private
        year = 2023;          // OK: public member now private
    }
};

// Usage
Car myCar;
// myCar.brand = "Honda";    // ERROR: public member now private
// myCar.year = 2023;        // ERROR: public member now private
// myCar.fuelLevel = 50.0;   // ERROR: protected member now private

Private Inheritance Behavior:

  • Public members become private in derived class
  • Protected members become private in derived class
  • Private members are not accessible in derived class

Default Inheritance Access โ€‹

cpp
class Base {
public:
    int publicMember;
protected:
    int protectedMember;
private:
    int privateMember;
};

// Class defaults to private inheritance
class Derived1 : Base {  // Same as : private Base
    // publicMember and protectedMember are now private
};

// Struct defaults to public inheritance
struct Derived2 : Base {  // Same as : public Base
    // publicMember and protectedMember remain public/protected
};

Constructor Initialization with Inheritance โ€‹

Calling Base Class Constructors โ€‹

cpp
class Shape {
protected:
    double area;
    std::string color;

public:
    Shape(const std::string& c) : color(c), area(0.0) {}

    virtual void calculateArea() = 0;  // Pure virtual function

    double getArea() const { return area; }
    std::string getColor() const { return color; }
};

class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r, const std::string& c)
        : Shape(c), radius(r) {  // Call base class constructor first
        calculateArea();  // Then call virtual function
    }

    void calculateArea() override {
        area = M_PI * radius * radius;
    }

    double getRadius() const { return radius; }
};

class Rectangle : public Shape {
private:
    double width, height;

public:
    Rectangle(double w, double h, const std::string& c)
        : Shape(c), width(w), height(h) {
        calculateArea();
    }

    void calculateArea() override {
        area = width * height;
    }

    double getWidth() const { return width; }
    double getHeight() const { return height; }
};

Key Points:

  • Base class constructor must be called in the initialization list
  • Order: Base class constructor executes first, then derived class members
  • Virtual functions can be called after base class is fully constructed

Multiple Constructors and Inheritance โ€‹

cpp
class Person {
protected:
    std::string name;
    int age;

public:
    Person() : name("Unknown"), age(0) {}
    Person(const std::string& n) : name(n), age(0) {}
    Person(const std::string& n, int a) : name(n), age(a) {}

    virtual void introduce() {
        std::cout << "I am " << name << ", age " << age << "." << std::endl;
    }
};

class Student : public Person {
private:
    std::string major;

public:
    // Default constructor
    Student() : Person(), major("Undeclared") {}

    // Constructor with name only
    Student(const std::string& n) : Person(n), major("Undeclared") {}

    // Constructor with name and age
    Student(const std::string& n, int a) : Person(n, a), major("Undeclared") {}

    // Constructor with all parameters
    Student(const std::string& n, int a, const std::string& m)
        : Person(n, a), major(m) {}

    void introduce() override {
        std::cout << "I am " << name << ", age " << age 
                  << ", studying " << major << "." << std::endl;
    }
};

The "Is-A" Relationship โ€‹

Inheritance should model a true "is-a" relationship, not just code reuse:

Good Inheritance Examples โ€‹

cpp
// Car IS-A Vehicle
class Vehicle {
public:
    virtual void start() = 0;
    virtual void stop() = 0;
};

class Car : public Vehicle {
public:
    void start() override { std::cout << "Car engine starts." << std::endl; }
    void stop() override { std::cout << "Car engine stops." << std::endl; }
};

// Student IS-A Person
class Person {
public:
    virtual void work() = 0;
};

class Student : public Person {
public:
    void work() override { std::cout << "Student studies." << std::endl; }
};

// Circle IS-A Shape
class Shape {
public:
    virtual double getArea() = 0;
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    double getArea() override { return M_PI * radius * radius; }
};

Poor Inheritance Examples โ€‹

cpp
// WRONG: Stack IS-NOT-A Vector
class Vector {
    // Vector implementation
};

class Stack : public Vector {  // Bad inheritance!
    // Stack should contain a Vector, not inherit from it
};

// CORRECT: Stack HAS-A Vector
class Stack {
private:
    std::vector<int> data;  // Composition, not inheritance

public:
    void push(int value) { data.push_back(value); }
    int pop() {
        int value = data.back();
        data.pop_back();
        return value;
    }
};

// WRONG: Employee IS-NOT-A Company
class Company {
    // Company implementation
};

class Employee : public Company {  // Bad inheritance!
    // Employee works for a company, doesn't inherit from it
};

// CORRECT: Employee HAS-A Company reference
class Employee {
private:
    Company* employer;  // Association, not inheritance

public:
    Employee(Company* comp) : employer(comp) {}
};

Polymorphism Basics โ€‹

Function Overriding โ€‹

cpp
class Base {
public:
    virtual void display() {
        std::cout << "Base class display" << std::endl;
    }

    void nonVirtual() {
        std::cout << "Base class non-virtual" << std::endl;
    }
};

class Derived : public Base {
public:
    void display() override {  // Override virtual function
        std::cout << "Derived class display" << std::endl;
    }

    void nonVirtual() {  // Hide non-virtual function
        std::cout << "Derived class non-virtual" << std::endl;
    }
};

// Usage
Base* basePtr = new Derived();
basePtr->display();      // Calls Derived::display() (polymorphism)
basePtr->nonVirtual();   // Calls Base::nonVirtual() (no polymorphism)

Virtual Functions and Polymorphism โ€‹

cpp
class Animal {
public:
    virtual void makeSound() {
        std::cout << "Some animal sound" << std::endl;
    }

    virtual ~Animal() {}  // Virtual destructor for proper cleanup
};

class Dog : public Animal {
public:
    void makeSound() override {
        std::cout << "Woof!" << std::endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override {
        std::cout << "Meow!" << std::endl;
    }
};

// Polymorphic behavior
void animalSound(Animal* animal) {
    animal->makeSound();  // Calls the appropriate derived class function
}

// Usage
Animal* animals= {new Dog(), new Cat(), new Dog()};
for (Animal* animal : animals) {
    animalSound(animal);  // Different sounds for different animals
}

Common Inheritance Patterns โ€‹

1. Interface Inheritance (Abstract Classes) โ€‹

cpp
class Drawable {
public:
    virtual void draw() = 0;  // Pure virtual function
    virtual ~Drawable() = default;
};

class Circle : public Drawable {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    void draw() override {
        std::cout << "Drawing circle with radius " << radius << std::endl;
    }
};

class Rectangle : public Drawable {
private:
    double width, height;

public:
    Rectangle(double w, double h) : width(w), height(h) {}

    void draw() override {
        std::cout << "Drawing rectangle " << width << "x" << height << std::endl;
    }
};

2. Implementation Inheritance โ€‹

cpp
class Logger {
protected:
    std::string logLevel;

public:
    Logger(const std::string& level) : logLevel(level) {}

    void log(const std::string& message) {
        std::cout << "[" << logLevel << "] " << message << std::endl;
    }

    void setLogLevel(const std::string& level) {
        logLevel = level;
    }
};

class FileLogger : public Logger {
private:
    std::string filename;

public:
    FileLogger(const std::string& level, const std::string& file)
        : Logger(level), filename(file) {}

    void log(const std::string& message) {
        Logger::log(message);  // Call base class implementation
        // Add file-specific logging logic
        std::cout << "Also writing to file: " << filename << std::endl;
    }
};

3. Multiple Inheritance (Basic) โ€‹

cpp
class Printable {
public:
    virtual void print() = 0;
    virtual ~Printable() = default;
};

class Drawable {
public:
    virtual void draw() = 0;
    virtual ~Drawable() = default;
};

class Shape : public Printable, public Drawable {
private:
    std::string color;

public:
    Shape(const std::string& c) : color(c) {}

    void print() override {
        std::cout << "Printing " << color << " shape" << std::endl;
    }

    void draw() override {
        std::cout << "Drawing " << color << " shape" << std::endl;
    }
};

Best Practices โ€‹

1. Use Public Inheritance for "Is-A" Relationships โ€‹

cpp
// Good: Car IS-A Vehicle
class Car : public Vehicle { /* ... */ };

// Avoid: Implementation inheritance without "is-a" relationship
// class MyClass : private UtilityClass { /* ... */ };

2. Make Destructors Virtual โ€‹

cpp
class Base {
public:
    virtual ~Base() {}  // Virtual destructor for proper cleanup
};

class Derived : public Base {
public:
    ~Derived() override {
        // Cleanup derived class resources
    }
};

3. Use Override Keyword โ€‹

cpp
class Base {
public:
    virtual void function() {}
};

class Derived : public Base {
public:
    void function() override {  // Compiler checks for correct override
        // Implementation
    }
};

4. Keep Inheritance Hierarchies Shallow โ€‹

cpp
// Good: Shallow hierarchy
class Animal { /* ... */ };
class Mammal : public Animal { /* ... */ };
class Dog : public Mammal { /* ... */ };

// Avoid: Deep hierarchies
// class Animal { /* ... */ };
// class Mammal : public Animal { /* ... */ };
// class Carnivore : public Mammal { /* ... */ };
// class Canine : public Carnivore { /* ... */ };
// class Dog : public Canine { /* ... */ };

Summary โ€‹

Inheritance is a powerful tool for creating class hierarchies and promoting code reuse:

Key Concepts:

  • "Is-A" Relationship: Derived class is a specialized version of base class
  • Code Reuse: Inherit functionality without rewriting
  • Polymorphism: Use derived classes where base classes are expected

Access Specifiers:

  • Public Inheritance: Preserves access levels (most common)
  • Protected Inheritance: Makes public members protected
  • Private Inheritance: Makes all members private

Best Practices:

  • Use public inheritance for true "is-a" relationships
  • Call base class constructors in initialization lists
  • Make destructors virtual for proper cleanup
  • Use override keyword for clarity and safety
  • Keep hierarchies shallow for maintainability

When to Use:

  • Model real-world relationships (Car is-a Vehicle)
  • Share common interface (all shapes can be drawn)
  • Reuse implementation (common logging functionality)
  • Enable polymorphism (different behaviors for same interface)

Understanding inheritance provides the foundation for more advanced OOP concepts like virtual functions, abstract classes, and design patterns. It's essential for building flexible, maintainable C++ applications.

Questions โ€‹

Q: What is the main purpose of inheritance in C++?

Inheritance allows you to create a hierarchy of related classes where derived classes inherit properties and behavior from base classes. This promotes code reuse by allowing derived classes to use base class functionality without rewriting it, and enables polymorphism.

Q: What is the difference between public, protected, and private inheritance?

Public inheritance preserves the original access levels (public stays public, protected stays protected). Protected inheritance makes public members become protected in the derived class. Private inheritance makes all public and protected members become private in the derived class.

Q: What happens when you don't specify an access specifier for inheritance?

When you don't specify an access specifier for inheritance, it defaults to private inheritance for classes and public inheritance for structs. This follows the same pattern as member access - classes default to private, structs default to public.

Q: How do you call a base class constructor from a derived class constructor?

You call a base class constructor by including it in the derived class constructor's initialization list using the base class name. For example: DerivedClass() : BaseClass(42) {} calls BaseClass's constructor with parameter 42.

Q: What is the 'is-a' relationship in inheritance?

The 'is-a' relationship means that a derived class is a specialized version of its base class. For example, a Car 'is-a' Vehicle, meaning Car inherits from Vehicle and can be used wherever a Vehicle is expected.