Chapter 12

Inheritance

Public, protected and private inheritance, base class initialization, overriding, slicing, multiple inheritance, and object initialization rules.

In this chapter

  1. Implementing Inheritance
  2. Access Specifiers in Inheritance
  3. Base Class Initialization
  4. Overriding Base Methods
  5. Private & Protected Inheritance
  6. Object Slicing
  7. Multiple Inheritance & final
  8. Avoiding Name Hiding
  9. Inheritance Meanings
  10. Copy & Assignment in Derived Classes
1

Implementing Inheritance

Inheritance models an "is-a" relationship between classes. A derived class inherits the interface and (optionally) the implementation of its base class.

// Syntax: class Derived : access-specifier Base
class Student {
public:
    void GetAge();
};

class Ramy : public Student {
    // Ramy "is a" Student — inherits GetAge()
};

The access specifier defaults to private for classes and public for structs. In practice, public inheritance is by far the most common.


2

Access Specifiers in Inheritance

Inherited classes share public data members and functions. Use the protected keyword to specify that certain attributes should only be accessible by class members — not external code, but visible to derived classes.

Base memberIn public inheritanceIn protected inheritanceIn private inheritance
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateinaccessibleinaccessibleinaccessible
A good way to make protected members effectively private to the outside world while still accessible within the hierarchy is to use public inheritance and mark the members protected in the base class.

3

Base Class Initialization

If the base class has no default constructor, you must explicitly initialize it from the derived constructor's member initializer list:

class Base {
public:
    Base(int x) { /* ... */ }
};

class Derived : public Base {
public:
    Derived() : Base(25) {  // passes 25 up to Base's constructor
        // ...
    }
};
You can use list initialization on a derived class constructor to initialize a base class member. This is also a good way to make protected members effectively private — initialize them through the base constructor and expose no setters.

4

Overriding Base Methods

If a derived class implements the same function with the same signature, it overrides the base version. The base version can still be called explicitly with the scope operator:

class Student {
public:
    void GetAge();
};

class Ramy : public Student {
public:
    void GetAge();  // overrides Student::GetAge
};

Ramy me;
me.GetAge();           // calls Ramy's version
me.Student::GetAge(); // explicitly calls Student's version
Overloading one version of a function in a derived class "hides" all other overloads of that function from the base. See Avoiding Name Hiding for the fix.

5

Private & Protected Inheritance

Private Inheritance

Models a "has-a" relationship. The derived class inherits everything from the base, but all inherited attributes are treated as private within the derived class — nothing is exposed to the outside or further derived classes.

class Ramy : private Student {
    // All public/protected Student members become private here
    // External code and further subclasses cannot access them
};
When to use private inheritance
  • You want to reuse implementation but not expose the interface
  • You want to model "implemented in terms of" — often composition is a better alternative
When to use protected inheritance
  • Rare. Public/protected members of base become protected — accessible to further derived classes but not to the outside
  • This is when you'd need it if a class further down the chain needs access

6

Object Slicing

When a derived object is assigned to a base class variable by value, the derived-class-specific data is "sliced off" — only the base portion is copied:

Ramy me;
Student s = me;  // SLICED — Ramy-specific members are lost
Always pass or store polymorphic objects through pointers or references, never by value. Slicing silently discards the derived data and can lead to subtle bugs.
Student* ptr = &me;   // safe — no slicing, points to full Ramy object
Student& ref = me;    // safe — reference to full Ramy object

7

Multiple Inheritance & final

Multiple Inheritance

A class can inherit from more than one base class. Bases are constructed in the order they are listed:

class Ramy : public Student, public Husband {
    // Student constructed first, then Husband
};
Multiple inheritance can lead to the Diamond Problem when two base classes share a common ancestor. This is covered in Chapter 13 with virtual inheritance as the fix.

final Classes

Adding final to a class declaration prevents it from being used as a base class:

class Ramy final : public Student {
    // No class can inherit from Ramy
};

8

Avoiding Name Hiding

When a derived class overrides any overload of a function name, the compiler hides all other overloads from the base:

class Base {
public:
    virtual void f1();
    virtual void f1(int x);
};

class Derived : public Base {
public:
    virtual void f1(); // override
};

Derived d;
d.f1();     // OK — uses Derived's version
d.f1(5);   // ERROR — Base's f1(int) is hidden!

Fix it with a using declaration to bring all base overloads into scope:

class Derived : public Base {
public:
    using Base::f1;   // compiler now sees all f1 overloads from Base
    virtual void f1(); // override
};

9

Inheritance Meanings

Be intentional about what your inherited methods are saying to derived classes:

Function typeWhat it means to derived classes
Pure virtual (= 0)Inherit the interface only. You must provide an implementation.
VirtualInherit the interface + default implementation. You may override.
Non-virtualInherit the interface + mandatory implementation. Do not redefine.
Never redefine an inherited non-virtual function. Non-virtual functions are statically bound — redefining them in a derived class creates confusing behavior depending on whether the object is accessed through a base or derived pointer.

10

Copy & Assignment in Derived Classes

The compiler auto-generates copy constructors and assignment operators if needed, but they make no attempt to copy base class data members. You must explicitly call the base version:

Derived::Derived(const Derived& other) : Base(other) {
    // Base(other) invokes Base's copy constructor
    // then copy the Derived-specific members here
}

Derived& Derived::operator=(const Derived& other) {
    Base::operator=(other);  // invoke Base's assignment
    // then assign Derived-specific members
    return *this;
}
Copy and assignment are similar but not the same. Copy creates a brand new object; assignment modifies an existing one. If they share logic, factor it into a private helper — but never have one call the other directly.
← Chapter 11 ↑ Index Chapter 13 →