Mastering C++ Inheritance, Polymorphism, and Exceptions
Understanding Object-Oriented Inheritance
Inheritance is a core pillar of Object-Oriented Programming (OOP) that allows a new class (derived class) to inherit properties and behaviors (data members and member functions) from an existing class (base class). This promotes code reusability and establishes an “is-a” relationship between classes.
1. Base Class vs. Derived Class
- Base Class (Parent/Super Class): The existing class whose properties and methods are inherited.
- Derived Class (Child/Sub Class): The new class that inherits from the base class. It can also add its own unique features or override existing ones.
Accessing Base Class Members
How a derived class accesses base class members depends on the Access Specifier used during inheritance:
class Derived : access_specifier Base { ... };| Base Class Member | Public Inheritance | Protected Inheritance | Private Inheritance |
|---|---|---|---|
| Public | Stays Public | Becomes Protected | Becomes Private |
| Protected | Stays Protected | Becomes Protected | Becomes Private |
| Private | Inaccessible | Inaccessible | Inaccessible |
⚠️ Note: Private members of a base class are never directly accessible in the derived class. They can only be accessed via public or protected member functions of the base class.
2. Types of Inheritance
OOP supports multiple ways to structure relationships between classes.
1. Multilevel Inheritance
A class is derived from another derived class, creating a linear chain of inheritance.
- Structure: A → B → C
- Example: Vehicle → Car → SportsCar
2. Multiple Inheritance
A single derived class inherits from more than one base class.
- Structure: A and B → C
- Example: Teacher and Researcher → Professor
3. Hierarchical Inheritance
Multiple derived classes inherit from a single, common base class.
- Structure: A → B and A → C
- Example: Animal → Dog and Cat
4. Hybrid Inheritance
A combination of two or more types of inheritance. A classic example is combining hierarchical and multiple inheritance, often forming a “Diamond” structure.
3. The Diamond Problem & Virtual Base Class
When you combine hierarchical and multiple inheritance, you can run into a famous issue called The Diamond Problem.
The Problem
Imagine Class A has a member variable x.
- Class B and Class C both inherit from A.
- Class D inherits from both B and C.
Because of this, Class D receives two separate copies of Class A’s members (one via B, one via C). If you try to access x from an object of Class D, the compiler gets confused about which copy you mean, resulting in an ambiguity error.
The Solution: Virtual Base Class
To fix this, we use the virtual keyword when inheriting. This ensures that only one instance of the base class is shared among the child classes.
class A { public: int x; };
// Using virtual inheritance
class B : virtual public A { ... };
class C : virtual public A { ... };
class D : public B, public C { ... }; // D now has exactly ONE copy of x4. Abstract Class
An Abstract Class is a class designed specifically to act as a blueprint for other classes. You cannot create an object (instantiate) of an abstract class directly.
- How it’s made: In C++, a class becomes abstract if it contains at least one Pure Virtual Function.
- Purpose: It enforces a contract. Any derived class must provide an implementation for the pure virtual functions, or it will also become an abstract class.
class Shape { // Abstract Class
public:
// Pure virtual function
virtual void draw() = 0;
};
class Circle : public Shape {
public:
// Overriding the pure virtual function
void draw() override {
// Code to draw a circle
}
};Polymorphism and Its Types
Polymorphism—meaning “many forms”—is the ability of a message, data, or object to be processed in more than one form. In C++, it is broadly divided into two types: Compile-time and Runtime polymorphism.
1. Compile-Time Polymorphism (Static Binding)
The compiler selects the appropriate function or operator at compile time based on the arguments passed.
- Function Overloading: Multiple functions share the same name but have different parameter lists.
- Operator Overloading: Giving special meanings to standard operators (+, -, <<, etc.) when applied to user-defined data types.
2. Runtime Polymorphism (Dynamic Binding)
The function call is resolved at runtime based on the actual object being pointed to. This is achieved using Virtual Functions and pointers/references.
Virtual vs. Pure Virtual Functions
| Feature | Virtual Function | Pure Virtual Function |
|---|---|---|
| Definition | A member function in a base class that you expect to override. | A virtual function with no implementation that must be overridden. |
| Syntax | virtual void display() { … } | virtual void display() = 0; |
| Object Creation | You can create instances of the class. | Class becomes Abstract and cannot be instantiated. |
| Requirement | Overriding is optional. | Overriding is mandatory. |
Exception Handling in C++
Exception handling is a mechanism designed to handle runtime errors so that the program doesn’t crash unexpectedly.
The Exception Handling Model
C++ uses a structured, synchronous exception-handling model based on three main constructs: try, throw, and catch.
- try block: Wraps the segment of code where an exception might occur.
- throw statement: Used to intentionally signal the occurrence of an error.
- catch block: Placed after a try block to intercept and handle the thrown exception.
#include <iostream>
#include <stdexcept>
int divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Division by zero error!");
}
return a / b;
}
int main() {
try {
int result = divide(10, 0);
std::cout << "Result: " << result << std::endl;
}
catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}Advanced Exception Handling
1. Order of Catch Blocks
The compiler executes the first block that matches the type of the exception.
⚠️ Critical Rule: Always place catch blocks for derived (specific) exception classes before base (generic) exception classes.
2. Catching All Exceptions (catch(…))
The catch-all handler, denoted by three dots (…), must always be placed last in your chain of catch blocks.
3. Nested Try Blocks
You can place a try-catch block inside another. Use the throw; statement to rethrow an exception to the outer layer.
4. Handling Uncaught Exceptions
If no matching catch block is found, the runtime calls std::terminate(). You can customize this behavior using std::set_terminate().
