Inheritance — Base and Derived Classes, Access Control, Virtual Base Classes
In this tutorial, you will learn about Inheritance. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ inheritance enables code reuse and type hierarchy through base and derived class relationships with three modes of access inheritance: public, protected, and private.
What You'll Learn
You will derive classes from base classes, control inherited member access with public, protected, and private inheritance, understand construction and destruction order in hierarchies, use virtual base classes to solve the diamond problem, and compare inheritance styles with Java and C.
Why It Matters
Inheritance is a core OOP mechanism. It lets you define a general concept (e.g., Shape) and extend it into specific concepts (Circle, Rectangle). However, inheritance is often overused. Understanding when to inherit and when to compose is a mark of experienced C++ designers. Virtual base classes solve a real problem in complex hierarchies that appear in GUI frameworks and large library designs.
Learning Path
graph LR
A["14: Encapsulation"] --> B["15: Inheritance"]
B --> C["16: Polymorphism"]
C --> D["17: Abstract Classes"]
D --> E["18: Multiple Inheritance"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
style E fill:#4a90d9,stroke:#2c5f8a,color:#fff
Basic Inheritance
#include <iostream>
#include <string>
class Animal {
protected:
std::string name_;
public:
Animal(const std::string& name) : name_(name) {}
void eat() const {
std::cout << name_ << " eats\n";
}
virtual void speak() const {
std::cout << name_ << " makes a sound\n";
}
};
class Dog : public Animal {
public:
Dog(const std::string& name) : Animal(name) {}
void speak() const override {
std::cout << name_ << " barks\n";
}
void fetch() const {
std::cout << name_ << " fetches\n";
}
};
int main() {
Dog dog("Rex");
dog.eat(); // inherited from Animal
dog.speak(); // overridden
dog.fetch(); // Dog-specific
}
Inheritance Access Specifiers
#include <iostream>
class Base {
private:
int private_ = 1;
protected:
int protected_ = 2;
public:
int public_ = 3;
};
// Public inheritance: public -> public, protected -> protected
class PublicDerived : public Base {
public:
void show() {
// std::cout << private_; // Error
std::cout << protected_ << "\n"; // 2
std::cout << public_ << "\n"; // 3
}
};
// Protected inheritance: public -> protected, protected -> protected
class ProtectedDerived : protected Base {
public:
void show() {
std::cout << protected_ << "\n"; // 2 (still protected)
std::cout << public_ << "\n"; // 3 (now protected)
}
};
// Private inheritance: public -> private, protected -> private
class PrivateDerived : private Base {
public:
void show() {
std::cout << protected_ << "\n"; // 2 (now private)
std::cout << public_ << "\n"; // 3 (now private)
}
};
int main() {
PublicDerived pub;
pub.public_; // OK: still public
ProtectedDerived prot;
// prot.public_; // Error: now protected
PrivateDerived priv;
// priv.public_; // Error: now private
}
| Inheritance Mode | Base Public | Base Protected | Base Private |
|---|---|---|---|
public |
public | protected | inaccessible |
protected |
protected | protected | inaccessible |
private |
private | private | inaccessible |
Construction and Destruction Order
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructed\n"; }
~Base() { std::cout << "Base destroyed\n"; }
};
class Member {
public:
Member() { std::cout << "Member constructed\n"; }
~Member() { std::cout << "Member destroyed\n"; }
};
class Derived : public Base {
private:
Member m_;
public:
Derived() { std::cout << "Derived constructed\n"; }
~Derived() { std::cout << "Derived destroyed\n"; }
};
int main() {
Derived d;
}
Expected output:
Base constructed
Member constructed
Derived constructed
Derived destroyed
Member destroyed
Base destroyed
Order: Base class first, then members in declaration order, then derived class body. Destruction reverses this.
Calling Base Class Constructors
#include <iostream>
#include <string>
class Person {
private:
std::string name_;
int age_;
public:
Person(const std::string& name, int age)
: name_(name), age_(age) {}
void print() const {
std::cout << name_ << " (" << age_ << ")\n";
}
};
class Student : public Person {
private:
std::string studentId_;
public:
Student(const std::string& name, int age, const std::string& id)
: Person(name, age), studentId_(id) {}
void print() const {
Person::print();
std::cout << "ID: " << studentId_ << "\n";
}
};
int main() {
Student s("Alice", 20, "S12345");
s.print();
}
Virtual Base Classes
Virtual base classes solve the diamond problem where a class inherits from two classes that share a common base.
#include <iostream>
class Animal {
protected:
int age_ = 0;
public:
Animal() { std::cout << "Animal constructed\n"; }
};
// Virtual inheritance
class Mammal : virtual public Animal {
public:
Mammal() { std::cout << "Mammal constructed\n"; }
};
class Bird : virtual public Animal {
public:
Bird() { std::cout << "Bird constructed\n"; }
};
// Only one copy of Animal
class Bat : public Mammal, public Bird {
public:
Bat() { std::cout << "Bat constructed\n"; }
};
int main() {
Bat b;
b.age_ = 5; // No ambiguity: only one Animal subobject
}
Expected output:
Animal constructed
Mammal constructed
Bird constructed
Bat constructed
Without virtual, Bat would contain two Animal subobjects, and accessing age_ would be ambiguous. Virtual inheritance ensures a single shared Animal subobject.
Upcasting and Downcasting
#include <iostream>
class Base {
public:
virtual ~Base() = default;
};
class Derived : public Base {
public:
void derivedOnly() {
std::cout << "Derived function\n";
}
};
int main() {
Derived d;
// Upcast: always safe, implicit
Base* basePtr = &d;
// Downcast: requires explicit cast
// Base* baseOnly = new Base;
// Derived* bad = static_cast<Derived*>(baseOnly); // dangerous
// Safe downcast with dynamic_cast
Base* ptr = &d;
Derived* derivedPtr = dynamic_cast<Derived*>(ptr);
if (derivedPtr) {
derivedPtr->derivedOnly();
}
}
Use dynamic_cast for safe downcasting in polymorphic hierarchies. It returns nullptr on failure.
Common Mistakes
Mistake 1: Public Inheritance for Code Reuse Only
class Stack : public std::vector<int> { // Wrong: Stack is not a vector
public:
void push(int x) { push_back(x); }
};
Public inheritance models "is-a," not "has-a." Use composition instead.
Mistake 2: Slicing
Dog dog;
Animal animal = dog; // Slices off Dog-specific parts
Pass by pointer or reference to avoid slicing.
Mistake 3: Forgetting to Call Base Constructor
If the base class has no default constructor, the derived class must explicitly call a base constructor in its initializer list.
Mistake 4: Non-Virtual Destructor in Base
Always make base class destructors virtual when deletion through base pointers is possible.
Mistake 5: Overriding Non-Virtual Functions
class Base {
public:
void f() { std::cout << "Base\n"; }
};
class Derived : public Base {
public:
void f() { std::cout << "Derived\n"; } // hides, not overrides
};
Use virtual and override to ensure proper overriding.
Practice Questions
- What is the difference between public, protected, and private inheritance?
- In what order are base classes and members constructed?
- What problem do virtual base classes solve?
- What is object slicing and how do you prevent it?
- Write a class hierarchy for
Vehicle->Car->ElectricCar.
Challenge
Design a shape hierarchy with Shape as base, Circle and Rectangle as derived. Include virtual area() and perimeter() functions. Demonstrate polymorphism with a vector of Shape* pointers.
FAQ
Mini Project
Build a zoo animal hierarchy:
#include <iostream>
#include <vector>
#include <memory>
class Animal {
protected:
std::string name_;
public:
Animal(const std::string& name) : name_(name) {}
virtual ~Animal() = default;
virtual void speak() const = 0;
virtual void move() const {
std::cout << name_ << " moves\n";
}
};
class Lion : public Animal {
public:
Lion(const std::string& name) : Animal(name) {}
void speak() const override {
std::cout << name_ << " roars\n";
}
};
class Snake : public Animal {
public:
Snake(const std::string& name) : Animal(name) {}
void speak() const override {
std::cout << name_ << " hisses\n";
}
void move() const override {
std::cout << name_ << " slithers\n";
}
};
int main() {
std::vector<std::unique_ptr<Animal>> zoo;
zoo.push_back(std::make_unique<Lion>("Simba"));
zoo.push_back(std::make_unique<Snake>("Kaa"));
for (const auto& animal : zoo) {
animal->speak();
animal->move();
}
}
What's Next
Inheritance builds class hierarchies. The next lesson covers polymorphism: virtual functions, the vtable, override and final specifiers, and how C++ achieves runtime dispatch.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro