What is Inheritance?
Inheritance is a core concept in object-oriented programming that allows a class (called the derived class or child class) to inherit properties and behaviors from another class (called the base class or parent class). It establishes an “is-a” relationship between classes, enabling code reuse and hierarchical classification.
Purpose of Inheritance
Inheritance serves several key purposes in object-oriented programming:
- Code Reusability: Avoid rewriting the same code by inheriting common functionality
- Extensibility: Extend existing classes without modifying their source code
- Hierarchical Organization: Create meaningful class hierarchies that model real-world relationships
- Polymorphism: Enable objects of different classes to be treated as objects of a common base class
- Specialization: Allow derived classes to specialize behavior of base classes
Basic Syntax for Inheritance
class BaseClass {
// Base class members
};
class DerivedClass : [access-specifier] BaseClass {
// Derived class members
};
The access-specifier can be:
public: Base class’s public members remain public, protected remain protectedprotected: Base class’s public and protected members become protectedprivate: Base class’s public and protected members become private
Simple Example of Inheritance
#include <iostream>
using namespace std;
// Base class
class Person {
protected:
string name;
int age;
public:
// Constructor
Person(string n, int a) : name(n), age(a) {}
void introduce() {
cout << "Hi, my name is " << name << " and I am " << age << " years old." << endl;
}
};
// Derived class
class Student : public Person {
private:
int studentId;
string course;
public:
// Constructor
Student(string n, int a, int id, string c)
: Person(n, a), studentId(id), course(c) {}
void study() {
cout << name << " is studying " << course << "." << endl;
}
void displayDetails() {
cout << "Student ID: " << studentId << endl;
cout << "Name: " << name << endl;
cout << "Age: " << age << endl;
cout << "Course: " << course << endl;
}
};
int main() {
// Create a Person object
Person person("John", 30);
person.introduce();
// Create a Student object
Student student("Alice", 20, 12345, "Computer Science");
student.introduce(); // Inherited from Person
student.study(); // Defined in Student
student.displayDetails();
return 0;
}
Output:
Hi, my name is John and I am 30 years old.
Hi, my name is Alice and I am 20 years old.
Alice is studying Computer Science.
Student ID: 12345
Name: Alice
Age: 20
Course: Computer Science
Access Specifiers and Inheritance
The way members of the base class are inherited depends on two factors:
- The access specifier in the base class (public, protected, private)
- The mode of inheritance (public, protected, private)
Inheritance Access Rules
| Base Class Member | public Inheritance | protected Inheritance | private Inheritance |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | Not accessible | Not accessible | Not accessible |
Example with Different Inheritance Modes
#include <iostream>
using namespace std;
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
public:
Base() : publicVar(1), protectedVar(2), privateVar(3) {}
void display() {
cout << "Base class: public = " << publicVar
<< ", protected = " << protectedVar
<< ", private = " << privateVar << endl;
}
};
// Public inheritance
class PublicDerived : public Base {
public:
void access() {
cout << "PublicDerived: can access public = " << publicVar;
cout << ", protected = " << protectedVar << endl;
// privateVar is not accessible here
}
};
// Protected inheritance
class ProtectedDerived : protected Base {
public:
void access() {
cout << "ProtectedDerived: can access public = " << publicVar;
cout << ", protected = " << protectedVar << endl;
// privateVar is not accessible here
}
};
// Private inheritance
class PrivateDerived : private Base {
public:
void access() {
cout << "PrivateDerived: can access public = " << publicVar;
cout << ", protected = " << protectedVar << endl;
// privateVar is not accessible here
}
};
int main() {
Base base;
PublicDerived pubDerived;
ProtectedDerived protDerived;
PrivateDerived privDerived;
// Access with Base object
base.publicVar = 10; // OK, public member
// base.protectedVar = 20; // Error, protected member
// base.privateVar = 30; // Error, private member
// Access with PublicDerived object
pubDerived.publicVar = 40; // OK, public inheritance keeps public members public
// pubDerived.protectedVar = 50; // Error, protected member
// Access with ProtectedDerived object
// protDerived.publicVar = 60; // Error, protected inheritance makes public members protected
// Access with PrivateDerived object
// privDerived.publicVar = 70; // Error, private inheritance makes public members private
// Call member functions
base.display();
pubDerived.display(); // Inherited as public
// protDerived.display(); // Error, inherited as protected
// privDerived.display(); // Error, inherited as private
// Call derived class member functions
pubDerived.access();
protDerived.access();
privDerived.access();
return 0;
}
Types of Inheritance
C++ supports five types of inheritance, allowing for various class relationships:
1. Single Inheritance
A derived class inherits from only one base class.
class Base { /* ... */ };
class Derived : public Base { /* ... */ };
2. Multiple Inheritance
A derived class inherits from two or more base classes.
class Base1 { /* ... */ };
class Base2 { /* ... */ };
class Derived : public Base1, public Base2 { /* ... */ };
3. Multilevel Inheritance
A derived class inherits from a base class, which itself is derived from another base class.
class GrandParent { /* ... */ };
class Parent : public GrandParent { /* ... */ };
class Child : public Parent { /* ... */ };
4. Hierarchical Inheritance
Multiple derived classes inherit from a single base class.
class Base { /* ... */ };
class Derived1 : public Base { /* ... */ };
class Derived2 : public Base { /* ... */ };
5. Hybrid Inheritance
A combination of two or more types of inheritance.
class Base { /* ... */ };
class Derived1 : public Base { /* ... */ };
class Derived2 : public Base { /* ... */ };
class DerivedFromBoth : public Derived1, public Derived2 { /* ... */ };
Constructor and Destructor Execution in Inheritance
When an object of a derived class is created:
- Base class constructor is called first
- Derived class constructor is called next
When an object of a derived class is destroyed:
- Derived class destructor is called first
- Base class destructor is called next
#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "Base constructor called" << endl;
}
~Base() {
cout << "Base destructor called" << endl;
}
};
class Derived : public Base {
public:
Derived() {
cout << "Derived constructor called" << endl;
}
~Derived() {
cout << "Derived destructor called" << endl;
}
};
int main() {
cout << "Creating Derived object:" << endl;
Derived d;
cout << "Program ending..." << endl;
return 0;
}
Output:
Creating Derived object:
Base constructor called
Derived constructor called
Program ending...
Derived destructor called
Base destructor called
Initializing Base Class with Parameters
To initialize a base class with specific values, you use the constructor initializer list:
#include <iostream>
using namespace std;
class Base {
private:
int value;
public:
Base(int v) : value(v) {
cout << "Base constructor called with value " << value << endl;
}
int getValue() const {
return value;
}
};
class Derived : public Base {
private:
int derivedValue;
public:
// Initialize base class with parameter
Derived(int b, int d) : Base(b), derivedValue(d) {
cout << "Derived constructor called with value " << derivedValue << endl;
}
void display() {
cout << "Base value: " << getValue() << endl;
cout << "Derived value: " << derivedValue << endl;
}
};
int main() {
Derived d(10, 20);
d.display();
return 0;
}
Output:
Base constructor called with value 10
Derived constructor called with value 20
Base value: 10
Derived value: 20
Method Overriding
Method overriding occurs when a derived class provides a specific implementation for a method that is already defined in its base class. This allows the derived class to customize or completely replace the behavior of the base class method.
#include <iostream>
using namespace std;
class Animal {
public:
void makeSound() {
cout << "Animal makes a sound" << endl;
}
void eat() {
cout << "Animal eats food" << endl;
}
};
class Dog : public Animal {
public:
// Override the makeSound method
void makeSound() {
cout << "Dog barks: Woof! Woof!" << endl;
}
// Not overriding eat method
};
class Cat : public Animal {
public:
// Override the makeSound method
void makeSound() {
cout << "Cat meows: Meow!" << endl;
}
// Override the eat method
void eat() {
cout << "Cat eats fish" << endl;
}
};
int main() {
Animal animal;
Dog dog;
Cat cat;
cout << "Animal sounds:" << endl;
animal.makeSound();
dog.makeSound();
cat.makeSound();
cout << "\nEating habits:" << endl;
animal.eat();
dog.eat(); // Uses Animal::eat()
cat.eat();
return 0;
}
Output:
Animal sounds:
Animal makes a sound
Dog barks: Woof! Woof!
Cat meows: Meow!
Eating habits:
Animal eats food
Animal eats food
Cat eats fish
Using the override Keyword (C++11)
In modern C++, it’s good practice to use the override keyword to explicitly indicate when you’re overriding a base class method. This helps catch errors at compile time.
class Base {
public:
virtual void foo() {
cout << "Base::foo()" << endl;
}
};
class Derived : public Base {
public:
void foo() override { // Compiler checks if this really overrides a base class method
cout << "Derived::foo()" << endl;
}
// This would cause a compile error because there's no base class method with this signature
// void fooo() override { }
};
Protected Members
Protected members are accessible within the class they are defined in, as well as in classes derived from that class, but not outside the class hierarchy.
#include <iostream>
using namespace std;
class Base {
private:
int privateVar;
protected:
int protectedVar;
public:
int publicVar;
Base() : privateVar(1), protectedVar(2), publicVar(3) {}
void display() {
cout << "Base: privateVar = " << privateVar
<< ", protectedVar = " << protectedVar
<< ", publicVar = " << publicVar << endl;
}
};
class Derived : public Base {
public:
void accessAndModify() {
// privateVar = 10; // Error, can't access private members
protectedVar = 20; // OK, can access protected members
publicVar = 30; // OK, can access public members
cout << "Derived: protectedVar = " << protectedVar
<< ", publicVar = " << publicVar << endl;
}
};
int main() {
Base b;
Derived d;
b.display();
// Can't access protected members from outside the class hierarchy
// cout << b.protectedVar; // Error
cout << b.publicVar << endl; // OK
d.accessAndModify();
d.display(); // Inherited from Base
return 0;
}
Output:
Base: privateVar = 1, protectedVar = 2, publicVar = 3
3
Derived: protectedVar = 20, publicVar = 30
Base: privateVar = 1, protectedVar = 20, publicVar = 30
Accessing Base Class Methods
Sometimes, a derived class needs to access the base class version of an overridden method. This can be done using the scope resolution operator (::).
#include <iostream>
using namespace std;
class Base {
public:
void display() {
cout << "Display method of Base class" << endl;
}
};
class Derived : public Base {
public:
// Override display method
void display() {
cout << "Display method of Derived class" << endl;
}
// Method that calls both versions
void showBoth() {
// Call derived class version
display();
// Call base class version
Base::display();
}
};
int main() {
Derived d;
cout << "Calling display directly:" << endl;
d.display(); // Calls Derived::display()
cout << "\nCalling both versions:" << endl;
d.showBoth();
cout << "\nExplicitly calling base version:" << endl;
d.Base::display(); // Explicitly call Base::display()
return 0;
}
Output:
Calling display directly:
Display method of Derived class
Calling both versions:
Display method of Derived class
Display method of Base class
Explicitly calling base version:
Display method of Base class
Common Pitfalls in Inheritance
- Slicing Problem: When a derived class object is assigned to a base class object, the derived class-specific members are “sliced off”.
Derived d;
Base b = d; // Slicing - only the Base part of d is copied to b
- Multiple Inheritance Diamond Problem: When a class inherits from two classes that both inherit from a common base class, ambiguity can arise.
class A { /* ... */ };
class B : public A { /* ... */ };
class C : public A { /* ... */ };
class D : public B, public C { /* ... */ }; // D has two copies of A's members
-
Incorrect Access Specifiers: Using the wrong access specifier can lead to unexpected behavior or restricted access.
-
Not Using Virtual Destructors: If a base class doesn’t have a virtual destructor, deleting a derived class object through a base class pointer can lead to undefined behavior.
Best Practices for Inheritance
-
Use public inheritance for “is-a” relationships (e.g., a Student is a Person).
-
Consider composition over inheritance for “has-a” relationships (e.g., a Car has an Engine).
-
Make destructors virtual in base classes that might be inherited from.
-
Use the override keyword (C++11) to explicitly indicate method overriding.
-
Avoid deep inheritance hierarchies as they can become hard to understand and maintain.
-
Think carefully about access specifiers to ensure appropriate encapsulation.
-
Use protected members for data that derived classes need to access but should be hidden from external code.
-
Initialize base classes properly using constructor initializer lists.
Summary
Inheritance is a powerful feature of object-oriented programming that allows classes to inherit attributes and behaviors from other classes. It promotes code reuse, establishes hierarchical relationships, and enables polymorphism. By understanding the different types of inheritance, access control, and best practices, you can effectively use inheritance to create well-structured, maintainable code in C++.