Skip to content

Inheritance — Base and Derived Classes, Access Control, Virtual Base Classes

DodaTech Updated 2026-06-28 7 min read

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

  1. What is the difference between public, protected, and private inheritance?
  2. In what order are base classes and members constructed?
  3. What problem do virtual base classes solve?
  4. What is object slicing and how do you prevent it?
  5. 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

What is the difference between inheritance and composition?

Inheritance models 'is-a' (Dog is an Animal). Composition models 'has-a' (Car has an Engine). Favor composition over inheritance for most design problems.

Can I inherit from multiple classes?

Yes. C++ supports multiple inheritance. Use it carefully to avoid the diamond problem and increased complexity.

What is the `final` specifier?

class Derived final : Base {}; prevents further inheritance. virtual void f() final; prevents further overriding in derived classes.

How does C++ inheritance compare to Java?

C++ supports multiple inheritance; Java uses single inheritance with interfaces. C++ has private/protected inheritance; Java only has public-like inheritance.

Can I change the access level of an inherited member?

Yes, with a using declaration: using Base::member; placed in the desired access section of the derived class.

What is the 'rule of zero'?

If your class does not manage a resource directly, do not define any of the special member functions (destructor, copy/move constructors, copy/move assignment). Let the compiler generate them.

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