Inheritance Concept

What is Inheritance?

Inheritance is a fundamental concept in object-oriented programming where a new class (called the derived class or subclass) is created from an existing class (called the base class or parent class). The derived class inherits all the properties and behaviors of the base class and can also have its own additional properties and behaviors.

Inheritance creates an “is-a” relationship between classes. For example, if “Car” inherits from “Vehicle”, it means a car “is a” vehicle.

Why Use Inheritance?

Inheritance serves several important purposes in object-oriented programming:

  1. Code Reusability: Reuse code from existing classes without rewriting it
  2. Extensibility: Extend the functionality of existing classes
  3. Hierarchical Classification: Organize classes in a hierarchical structure
  4. Method Overriding: Modify inherited behaviors to suit specific needs
  5. Polymorphic Behavior: Enable objects of different classes to be treated as objects of a common base class

Basic Inheritance Concepts

Base Class (Parent Class)

The class whose properties and methods are inherited by another class.

Derived Class (Child Class)

The class that inherits properties and methods from another class.

Superclass and Subclass

Alternative terms for base class and derived class, respectively.

”is-a” Relationship

Inheritance creates an “is-a” relationship between classes, meaning the derived class is a type of the base class.

Inheritance Syntax in C++

// Base class
class Vehicle {
protected:
    std::string brand;
    int year;
    
public:
    Vehicle(std::string b, int y) : brand(b), year(y) {}
    
    void honk() {
        std::cout << "Honk! Honk!" << std::endl;
    }
    
    void displayInfo() {
        std::cout << "Brand: " << brand << ", Year: " << year << std::endl;
    }
};

// Derived class
class Car : public Vehicle {
private:
    int numDoors;
    
public:
    Car(std::string b, int y, int doors) : Vehicle(b, y), numDoors(doors) {}
    
    void displayCarInfo() {
        displayInfo();  // Call base class method
        std::cout << "Number of doors: " << numDoors << std::endl;
    }
};

Access Control in Inheritance

The accessibility of inherited members in the derived class depends on:

  1. The access specifier used in the base class (public, protected, private)
  2. The inheritance mode used (public, protected, private)

Access Specifiers in Base Class:

  • public members: Accessible from anywhere
  • protected members: Accessible within the class and its derived classes
  • private members: Accessible only within the class itself

Inheritance Modes:

  • public inheritance: public → public, protected → protected, private → inaccessible
  • protected inheritance: public → protected, protected → protected, private → inaccessible
  • private inheritance: public → private, protected → private, private → inaccessible

Inheritance Mode Effects (Table)

Base Class MemberPublic InheritanceProtected InheritancePrivate Inheritance
PublicPublic in derivedProtected in derivedPrivate in derived
ProtectedProtected in derivedProtected in derivedPrivate in derived
PrivateNot accessibleNot accessibleNot accessible

Types of Inheritance (Preview)

C++ supports different types of inheritance structures:

  1. Single Inheritance: A derived class inherits from one base class
  2. Multiple Inheritance: A derived class inherits from multiple base classes
  3. Multilevel Inheritance: A derived class inherits from a class that itself inherits from another class
  4. Hierarchical Inheritance: Multiple derived classes inherit from a single base class
  5. Hybrid Inheritance: A combination of multiple inheritance types

(These inheritance types will be covered in more detail in Unit 3)

Constructor and Destructor Behavior in Inheritance

When you create an object of a derived class:

  1. Base class constructor is executed first
  2. Derived class constructor is executed next

When an object is destroyed:

  1. Derived class destructor is executed first
  2. Base class destructor is executed next

Constructor Calling in Inheritance

class Base {
public:
    Base() {
        std::cout << "Base constructor called" << std::endl;
    }
    
    ~Base() {
        std::cout << "Base destructor called" << std::endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        std::cout << "Derived constructor called" << std::endl;
    }
    
    ~Derived() {
        std::cout << "Derived destructor called" << std::endl;
    }
};

int main() {
    Derived d;
    // Output:
    // Base constructor called
    // Derived constructor called
    // Derived destructor called
    // Base destructor called
    
    return 0;
}

Calling Base Class Constructor Explicitly

To pass parameters to a base class constructor, use an initializer list:

class Base {
private:
    int value;
    
public:
    Base(int v) : value(v) {
        std::cout << "Base initialized with value: " << value << std::endl;
    }
};

class Derived : public Base {
private:
    int derivedValue;
    
public:
    // Call base class constructor with a parameter
    Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {
        std::cout << "Derived initialized with value: " << derivedValue << std::endl;
    }
};

Real-world Inheritance Example

// Base class
class Shape {
protected:
    double x, y;  // Position
    
public:
    Shape(double xPos, double yPos) : x(xPos), y(yPos) {}
    
    virtual void draw() {
        std::cout << "Drawing a shape at position (" << x << ", " << y << ")" << std::endl;
    }
    
    virtual double area() {
        return 0.0;  // Default implementation
    }
};

// Derived class 1
class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(double xPos, double yPos, double r) : Shape(xPos, yPos), radius(r) {}
    
    void draw() override {
        std::cout << "Drawing a circle at (" << x << ", " << y << ") with radius " 
                 << radius << std::endl;
    }
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

// Derived class 2
class Rectangle : public Shape {
private:
    double width, height;
    
public:
    Rectangle(double xPos, double yPos, double w, double h) 
        : Shape(xPos, yPos), width(w), height(h) {}
    
    void draw() override {
        std::cout << "Drawing a rectangle at (" << x << ", " << y << ") with width " 
                 << width << " and height " << height << std::endl;
    }
    
    double area() override {
        return width * height;
    }
};

Benefits of Inheritance

  1. Code Reuse: Reduces redundancy by reusing existing code
  2. Logical Organization: Creates clear, hierarchical relationships between concepts
  3. Extensibility: Makes it easy to add specialized classes without modifying existing code
  4. Maintainability: Changes to the base class automatically propagate to all derived classes
  5. Polymorphism Support: Enables polymorphic behavior through virtual methods

Challenges and Considerations

  1. Tight Coupling: Base and derived classes become dependent on each other
  2. Fragile Base Class Problem: Changes to base classes can unintentionally affect derived classes
  3. Diamond Problem: Ambiguity that can arise with multiple inheritance (covered in Unit 3)
  4. Deep Hierarchies: Overly deep inheritance trees can become difficult to understand
  5. Overuse: Inheritance should be used only for genuine “is-a” relationships

Inheritance vs. Composition

InheritanceComposition
”is-a” relationship”has-a” relationship
Tight couplingLoose coupling
Compile-time relationshipRun-time relationship
Derived class inherits all base class featuresComposed class selectively exposes features
Cannot change behavior at runtimeCan change behavior by changing component

Best Practices for Inheritance

  1. Follow the Liskov Substitution Principle: Derived class objects should be able to replace base class objects without affecting program behavior
  2. Keep inheritance hierarchies shallow: Limit to 2-3 levels when possible
  3. Prefer composition over inheritance when the relationship is not clearly “is-a”
  4. Use abstract base classes to define interfaces
  5. Design for inheritance or prohibit it: Either design your classes to be safely inherited from or prevent inheritance

Conclusion

Inheritance is a powerful mechanism in OOP that enables code reuse and creates a hierarchical relationship between classes. When used appropriately (for genuine “is-a” relationships), it can significantly improve code organization, reusability, and maintainability. However, it should be used judiciously, as composition often provides a more flexible alternative for “has-a” relationships.