In object-oriented programming, inheritance enables code reuse by allowing a new class (derived class) to acquire properties and behaviors from an existing class (base class). This is particularly useful when multiple classes share common functionality.
Consider a scenario where several course categories—Java, Python, and C++—on an educational platform all require identical UI components: header, footer, and navigation menu. Without inheritance, each class duplicates these methods:
#include <iostream>
class Java {
public:
void header() { std::cout << "Home, Courses, Login, Register..." << std::endl; }
void footer() { std::cout << "Help, Collaboration, Sitemap..." << std::endl; }
void navMenu() { std::cout << "Java, Python, C++..." << std::endl; }
void content() { std::cout << "Java tutorials" << std::endl; }
};
class Python {
public:
void header() { std::cout << "Home, Courses, Login, Register..." << std::endl; }
void footer() { std::cout << "Help, Collaboration, Sitemap..." << std::endl; }
void navMenu() { std::cout << "Java, Python, C++..." << std::endl; }
void content() { std::cout << "Python tutorials" << std::endl; }
};
class CPlusPlus {
public:
void header() { std::cout << "Home, Courses, Login, Register..." << std::endl; }
void footer() { std::cout << "Help, Collaboration, Sitemap..." << std::endl; }
void navMenu() { std::cout << "Java, Python, C++..." << std::endl; }
void content() { std::cout << "C++ tutorials" << std::endl; }
};
This redundancy is eliminated by extracting shared behavior into a base class:
#include <iostream>
class PlatformBase {
public:
void header() { std::cout << "Home, Courses, Login, Register..." << std::endl; }
void footer() { std::cout << "Help, Collaboration, Sitemap..." << std::endl; }
void navMenu() { std::cout << "Java, Python, C++..." << std::endl; }
};
class Java : public PlatformBase {
public:
void content() { std::cout << "Java tutorials" << std::endl; }
};
class Python : public PlatformBase {
public:
void content() { std::cout << "Python tutorials" << std::endl; }
};
class CPlusPlus : public PlatformBase {
public:
void content() { std::cout << "C++ tutorials" << std::endl; }
};
The syntax for inheritance is: class Derived : access_specifier Base. Here, PlatformBase is the base class, and Java, Python, and CPlusPlus are derived classes. The public access specifier ensures that public members of the base class remain accessible through the derived class objects.
Access Specifiers in Inheritance
Inheritance in C++ supports three access modes: public, protected, and private. These determine how base class members are perceived in the derived class:
| Inheritance Type | Base Public Member | Base Protected Member | Base Private Member |
|---|---|---|---|
| public | public | protected | inaccessible |
| protected | protected | protected | inaccessible |
| private | private | private | inaccessible |
Example demonstrating member accessibility:
#include <iostream>
class Base {
public:
int publicVar = 10;
protected:
int protectedVar = 20;
private:
int privateVar = 30;
};
class PublicDerived : public Base {
public:
void accessMembers() {
publicVar = 100; // OK
protectedVar = 200; // OK
// privateVar = 300; // Error: inaccessible
}
};
class ProtectedDerived : protected Base {
public:
void accessMembers() {
publicVar = 100; // becomes protected
protectedVar = 200; // remains protected
}
};
class PrivateDerived : private Base {
public:
void accessMembers() {
publicVar = 100; // becomes private
protectedVar = 200; // becomes private
}
};
class GrandChild : public PrivateDerived {
public:
void tryAccess() {
// publicVar = 500; // Error: inherited as private
}
};
Object Layout and Memory Model
When a derived class inherits from a base class, all non-private members of the base class are included in the derived class’s memory layout—even those marked private, which are inaccessible but still occupy space.
#include <iostream>
class Base {
public:
int x;
protected:
int y;
private:
int z;
};
class Derived : public Base {
public:
int w;
};
void test() {
std::cout << "Size of Derived: " << sizeof(Derived) << std::endl; // Typically 16 bytes on 64-bit systems
}
Using Visual Studio’s cl /d1 reportSingleClassLayout command-line tool reveals the internal structure:
- Compile with:
cl /d1 reportSingleClassLayoutDerived Derived.cpp - Output shows:
Base::x,Base::y,Base::z, andDerived::win sequential memory layout
Constructor and Destructor Order
During object creation, base class constructors are called before derived class constructors. During destruction, the reverse occurs:
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructor" << std::endl; }
~Base() { std::cout << "Base destructor" << std::endl; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived constructor" << std::endl; }
~Derived() { std::cout << "Derived destructor" << std::endl; }
};
void test() {
Derived d;
// Output:
// Base constructor
// Derived constructor
// Derived destructor
// Base destructor
}
Handling Name Conflicts
When a derived class declares a member with the same name as a base class member, the derived version hides the base version. To access the base member, use the scope resolution operator (::):
#include <iostream>
class Base {
public:
int value = 10;
void show() { std::cout << "Base::show()" << std::endl; }
};
class Derived : public Base {
public:
int value = 100;
void show() { std::cout << "Derived::show()" << std::endl; }
};
void test() {
Derived d;
std::cout << d.value << std::endl; // 100 (derived)
std::cout << d.Base::value << std::endl; // 10 (base)
d.show(); // Derived::show()
d.Base::show(); // Base::show()
}
Important: Overloaded functions in the base class are entirely hidden if any function with the same name exists in the derived class:
class Base {
public:
void display() { std::cout << "Base display()" << std::endl; }
void display(int n) { std::cout << "Base display(" << n << ")" << std::endl; }
};
class Derived : public Base {
public:
void display() { std::cout << "Derived display()" << std::endl; }
};
void test() {
Derived d;
d.display(); // OK: calls Derived::display()
// d.display(5); // ERROR: hidden by Derived::display()
d.Base::display(5); // OK: explicitly call base version
}
Static Members in Inheritance
Static members follow the same name-hiding rules as non-static members:
#include <iostream>
class Base {
public:
static int count;
static void print() { std::cout << "Base::print()" << std::endl; }
};
int Base::count = 10;
class Derived : public Base {
public:
static int count;
static void print() { std::cout << "Derived::print()" << std::endl; }
};
int Derived::count = 20;
void test() {
Derived d;
std::cout << d.count << std::endl; // 20
std::cout << Derived::count << std::endl; // 20
std::cout << Base::count << std::endl; // 10
std::cout << Derived::Base::count << std::endl; // 10
d.print(); // Derived::print()
d.Base::print(); // Base::print()
Derived::print(); // Derived::print()
Derived::Base::print(); // Base::print()
}
Multiple Inheritance
C++ allows a class to inherit from multiple base classes:
#include <iostream>
class ModuleA {
public:
int id = 100;
};
class ModuleB {
public:
int id = 200;
};
class Combined : public ModuleA, public ModuleB {
public:
void display() {
std::cout << ModuleA::id << std::endl; // Disambiguate
std::cout << ModuleB::id << std::endl; // Disambiguate
}
};
Virtual Inheritance and Diamond Problem
When two classes inherit from a common base, and a fourth class inherits from both, a "diamond" structure forms. This leads to duplicate base class subobjects, causing ambiguity and wasted memory.
#include <iostream>
class Animal {
public:
int age;
};
class Sheep : public Animal {};
class Camel : public Animal {};
class SheepCamel : public Sheep, public Camel {}; // Diamond problem
void test() {
SheepCamel sc;
// sc.age = 5; // ERROR: ambiguous
sc.Sheep::age = 5; // OK, but wastes memory
sc.Camel::age = 6; // OK, but separate copy
}
Use virtual inheritance to ensure only one enstance of the shared base class:
#include <iostream>
class Animal {
public:
int age;
};
class Sheep : virtual public Animal {};
class Camel : virtual public Animal {};
class SheepCamel : public Sheep, public Camel {};
void test() {
SheepCamel sc;
sc.age = 5; // Now unambiguous and single instance
std::cout << "Age: " << sc.age << std::endl;
}
With virtual inheritance, the compiler introduces a virtual base pointer (vbptr) and a virtual base table (vbtable) to manage offsets to the shared base class. Tools like cl /d1 reportSingleClassLayout will show these internal structures, confirming a single Animal subobject shared among all derived classes.