Inheritance is a core mechanism in object-oriented programming that enables code reuse by allowing new classes to be built upon existing ones. A derived class inherits members from a base class and can extend or modify its behavior.
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
Person(string name = "Unknown", int age = 0)
: _name(name), _age(age) {
cout << "Person constructed\n";
}
void display() const {
cout << "Name: " << _name << ", Age: " << _age << endl;
}
protected:
string _name;
int _age;
};
class Student : public Person {
public:
Student(string name, int age, int id)
: Person(name, age), _studentId(id) {
cout << "Student constructed\n";
}
void display() const {
Person::display();
cout << "Student ID: " << _studentId << endl;
}
private:
int _studentId;
};
class Teacher : public Person {
public:
Teacher(string name, int age, int id)
: Person(name, age), _teacherId(id) {
cout << "Teacher constructed\n";
}
void display() const {
Person::display();
cout << "Teacher ID: " << _teacherId << endl;
}
private:
int _teacherId;
};
int main() {
Student s("Alice", 20, 1001);
Teacher t("Dr. Smith", 45, 2002);
cout << "\nStudent:\n";
s.display();
cout << "\nTeacher:\n";
t.display();
return 0;
}
Access Control in Inheritance
The accessibility of base class members in the derived class depends on both the original access specifier and the inheritance type:
| Base Member / Inheritance | public |
protected |
private |
|---|---|---|---|
public |
public | protected | private |
protected |
protected | protected | private |
private |
inaccessible | inaccessible | inaccessible |
Key points:
privatemembers of the base class are never accessible in the derived class.protectedmembers allow access within derived classes but not from outside.- The effective access level is the stricter of the member’s original access and the inheritance type.
- Default inheritance is
privateforclassandpublicforstruct, but explicit specification is recommended. publicinheritance is most common;protected/privateinheritance is rarely used due to limited extensibility.
Object Assignment and Slicing
A derived class object can be assigned to a base class object, pointer, or reference—this is known as object slicing, where only the base part is copied. The reverse (assigning a base object to a derived one) is not allowed. A base pointer pointing to a derived object can be cast back to a derived pointer, but only if it actually points to such an object.
Name Hiding in Inheritance
Base and derived classes have separate scopes. If a derived class declares a member with the same name as one in the base class, the base member is hidden (not overloaded). To access it, use the scope resolution operator (Base::member). Note that function hiding occurs solely based on name matching across scopes, unlike overloading which requires same scope and differing parameters.
Default Member Functions in Derived Classes
- The derived constructor must initialize the base subobject, typically via the initializer list.
- Copy constructor, assignment operator, and destructor of the derived class automatically invoke their base counterparts.
- Construction order: base class first, then derived class.
- Destruction order: derived class first, then base class—ensuring resources are released safely.
This order prevents issues like accessing already-deleted base resources during derived destruction.
Friends and Static Members
Friendship is not inherited: a friend of the base class isn’t automatically a friend of the derived class. Static members are shared across the entire inheritance hierarchy—only one instance exists regardless of how many derived classes exist.
Multiple and Virtual Inheritance
Single inheritance: one base class. Multiple inheritance: multiple direct base classes. Diamond inheritance arises when two classes inherit from the same base, and a fourth inherits from both—leading to duplicate base subobjects and ambiguity.
Example of diamond problem:
class Person { protected: string _name; };
class Student : public Person { /* ... */ };
class Teacher : public Person { /* ... */ };
class TA : public Student, public Teacher {
// _name appears twice → ambiguity
};
Virtual inheritance resolves this by ensuring only one instance of the shared base class exists:
class Person { public: int id; };
class Student : virtual public Person {};
class Teacher : virtual public Person {};
class TA : public Student, public Teacher {};
Under the hood, virtual inheritance uses virtual base pointers and virtual base tables to compute offsets to the shared base subobject at runtime.
Inheritance vs. Composition
Prefer composition over inheritance when possible:
- Inheritance models an is-a relationship and exposes implementation details (white-box reuse), increasing coupling.
- Composition models a has-a relationship, encapsulates components (black-box reuse), and reduces dependencies.
While inheritance is essential for polymorphism, excessive or inappropriate use—especially multiple or diamond inheritance—complicates design and impacts performance. Modern OOP languages often avoid multiple inheritance entirely.