Inheritance is one of the core pillars of object-oriented programming. It enables a class to acquire properties and behaviors from another class, promoting code reuse and reducing redundancy.
When multiple classes share common attributes or methods but also define their own unique features, inheritance allows you to extract the shared parts into a base class. Derived classes then inherit this common functionality while adding their specific logic.
Inheritance Syntax and Access Specifiers
The general syntax for inheritance in C++ is:
class DerivedClass : access-specifier BaseClass { /* ... */ };
There are three types of inheritance based on the access-specifier:
- Public inheritance: Public members of the base remain public; protected remain protected.
- Protected inheritance: Public and protected members of the base become protected in the derived class.
- Private inheritance: Public and protected members of the base become private in the derived class.
Note: Private members of the base class are never directly accessible in the derived class, regardless of the inheritance type—though they are still part of the derived object's memory layout.
Constructor and Destructor Order
During object creation, constructors are called in the order from base to derived. During destruction, the reverse happens: derived destructor runs first, followed by the base destructor.
class Base {
public:
Base() { std::cout << "Base constructed\n"; }
~Base() { std::cout << "Base destroyed\n"; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructed\n"; }
~Derived() { std::cout << "Derived destroyed\n"; }
};
Handling Name Conflicts
If a derived class declares a member with the same name as one in the base class, the derived version hides the base version. To access the base class member, use the scope resolution operator (::):
class Base {
public:
int value = 10;
void display() { std::cout << "Base\n"; }
};
class Derived : public Base {
public:
int value = 20;
void display() { std::cout << "Derived\n"; }
};
// Usage:
Derived d;
std::cout << d.value; // 20
std::cout << d.Base::value; // 10
d.display(); // "Derived"
d.Base::display(); // "Base"
Static Members and Name Hiding
Static members follow the same hiding rules as non-static ones. They can be accessed either through an object or directly via the class name:
class Base {
public:
static int count;
static void report() { std::cout << "Base report\n"; }
};
int Base::count = 100;
class Derived : public Base {
public:
static int count;
static void report() { std::cout << "Derived report\n"; }
};
int Derived::count = 200;
// Access examples:
Derived obj;
std::cout << obj.count; // 200
std::cout << obj.Base::count; // 100
std::cout << Derived::Base::count; // 100
Derived::report(); // "Derived report"
Derived::Base::report(); // "Base report"
Multiple Inheritance
C++ supports inheriting from more than one base class:
class A { public: int x; };
class B { public: int x; };
class C : public A, public B {
public:
void setValues() {
A::x = 1;
B::x = 2;
}
};
However, multiple inheritance can lead to ambiguity if both bases define members with the same name—requiring explicit qualification. Due to complexity and potential issues, it's often discouraged in practice.
Diamond Problem and Virtual Inheriatnce
The "diamond problem" arises when two classes inherit from a common base, and a fourth class inherits from both. Without intervention, the common base is duplicated in the final object.
To resolve this, C++ provides virtual inheritance:
class Animal {
public:
int id;
};
class Sheep : virtual public Animal {};
class Camel : virtual public Animal {};
class Llama : public Sheep, public Camel {}; // Only one Animal subobject
With virtual, Animal becomes a virtual base class, ensuring only one instance exists in Llama. This avoids ambiguity and data duplication.