Inheritance
Build a new class on top of an existing one, reusing and extending its members — the mechanism that expresses an is-a relationship between types before polymorphism enters the picture.
Inheritance lets one class (the derived class) be defined in terms of another (the base class), automatically acquiring the base class's members and adding or overriding behavior of its own. It is one of the ways C++ supports code reuse and models relationships between types, and it lays the groundwork for polymorphism — though dynamic dispatch itself, virtual functions, and abstract classes are a separate, dedicated topic. This page covers what a base/derived relationship is, how access control and construction order work across it, and where inheritance's limits are.
What Is Inheritance?
Inheritance is a relationship between two classes in which a derived class is defined to include the members of a base class, plus whatever additional members the derived class declares itself. The derived class is said to inherit from, or derive from, the base class.
Dog inherits eat() from Animal and adds its own bark() — a Dog object has both.
class Animal
{
public:
void eat() { std::cout << "eating\n"; }
};
class Dog : public Animal
{
public:
void bark() { std::cout << "barking\n"; }
};
Dog d;
d.eat(); // inherited from Animal
d.bark(); // declared directly in DogBase Class and Derived Class
The class being inherited from is the base class (also called a superclass or parent class). The class doing the inheriting is the derived class (also called a subclass or child class). A derived class can itself serve as the base class for a further derived class, forming a hierarchy.
The arrow points from derived to base, following the convention used in most inheritance diagrams: Dog is-a kind of Animal.
Animal (base class)
^
| inherits from
|
Dog (derived class)Basic Syntax
A derived class names its base class after a colon following the class name, along with an access specifier that controls how the base class's members are exposed through the derived class.
public Base means Derived inherits from Base, and the access-level relationships described in the next few sections apply.
class Derived : public Base
{
// additional members
};The Is-A Relationship
Public inheritance is generally used to model an is-a relationship: a Dog is an Animal, a Circle is a Shape. If that phrasing does not genuinely hold for two types, public inheritance is usually the wrong tool — a has-a relationship (composition, covered later) is more appropriate instead.
Public Inheritance
With public inheritance (class Derived : public Base), a base class's public members remain public in the derived class, and its protected members remain protected. This is by far the most common form of inheritance, and the one that expresses an is-a relationship in the usual sense — a Dog object can be used anywhere an Animal is expected.
Public inheritance preserves each inherited member's original accessibility.
class Animal
{
public:
void eat();
protected:
int age;
};
class Dog : public Animal
{
// eat() is public here too
// age is protected here too
};Protected and Private Inheritance
protected inheritance (class Derived : protected Base) makes the base class's public members become protected in the derived class. private inheritance (class Derived : private Base, and the default when no access specifier is written for a class) makes both public and protected base members become private in the derived class. Both are used far less often than public inheritance, since they express an implementation-reuse relationship rather than an is-a relationship — code outside the derived class cannot treat a Derived as a Base through either of these forms.
Car reuses Engine's implementation without exposing an Engine interface to Car's own users — private inheritance is one way to express 'implemented in terms of' rather than 'is a kind of'.
class Engine
{
public:
void start();
};
class Car : private Engine // "implemented in terms of", not "is-a"
{
public:
void drive()
{
start(); // Car can still use start() internally
}
};
Car c;
// c.start(); // error: start() is private in CarAccess Specifiers Under Inheritance: Full Table
A base class's private members are never directly accessible in a derived class regardless of the inheritance access specifier — only the base class's own member functions can access them. The inheritance specifier only affects how the base's public and protected members are re-exposed through the derived class.
Base member access | public inheritance | protected inheritance | private inheritance
---------------------+------------------------+---------------------------+-----------------------
public | public | protected | private
protected | protected | protected | private
private | not accessible | not accessible | not accessibleWhy protected Exists
protected sits between public and private: a protected member is inaccessible from outside the class hierarchy (like private), but accessible from derived classes (unlike private). It exists specifically to let a base class expose implementation details to its derived classes without exposing them to arbitrary outside code.
Dog::birthday() can modify age directly because age is protected in Animal, not private; code outside the Animal/Dog hierarchy still cannot access age.
class Animal
{
protected:
int age; // derived classes may use age directly
public:
Animal(int age) : age(age) {}
};
class Dog : public Animal
{
public:
Dog(int age) : Animal(age) {}
void birthday()
{
++age; // OK: age is protected, accessible from a derived class
}
};What a Derived Class Inherits
A derived class inherits the base class's data members and member functions (subject to the accessibility rules above). It does not inherit the base class's constructors, destructor, or the special member functions as callable members with the derived class's own name automatically usable through simple name lookup in every case — construction and destruction of the base part are handled through the mechanisms described in the following sections, not by treating a base constructor as if it were a Derived constructor.
Dog must define its own constructor that explicitly initializes its Animal base; Animal's constructor is not automatically usable as if it were Dog's.
class Animal
{
public:
Animal(std::string name) : name(std::move(name)) {}
void eat() { std::cout << name << " eats\n"; }
private:
std::string name;
};
class Dog : public Animal
{
public:
Dog(std::string name) : Animal(std::move(name)) {} // must forward explicitly
};Base-Class Initialization
A derived class's constructor initializes its base class subobject by naming the base class in its member initializer list, exactly as it would name a data member. If the base class has no default constructor, the derived class must invoke a specific base constructor explicitly this way.
Animal(age) in Dog's initializer list selects and runs Animal's constructor before Dog's own member breed is initialized.
If a derived constructor's initializer list does not mention the base class, the base class's default constructor is used instead — which is only valid if the base class actually has one.
class Animal
{
public:
explicit Animal(int age) : age(age) {}
protected:
int age;
};
class Dog : public Animal
{
public:
Dog(int age, std::string breed)
: Animal(age), breed(std::move(breed))
{
}
private:
std::string breed;
};Constructor Order in Hierarchies
When a derived object is constructed, the base class subobject is always fully constructed first, then the derived class's own members are initialized (in declaration order), and finally the derived class constructor's body runs. This order is fixed by the language and does not depend on the order names appear in the initializer list.
The base subobject always finishes construction before the derived part begins, so a derived class's own members and constructor body can safely rely on the base part already being fully valid.
Animal's constructor always runs before Dog's, regardless of what the code looks like — this is guaranteed by the language, not by the order of statements.
Constructing a Dog:
1. Animal's constructor runs completely
2. Dog's members are initialized, in declaration order
3. Dog's constructor body runs
+-------------------+
| Animal part | <- constructed FIRST
+-------------------+
| Dog's own members | <- constructed SECOND
+-------------------+class Animal
{
public:
Animal() { std::cout << "Animal constructed\n"; }
};
class Dog : public Animal
{
public:
Dog() { std::cout << "Dog constructed\n"; }
};
Dog d;
// output:
// Animal constructed
// Dog constructedDestructor Order in Hierarchies
Destruction happens in exactly the reverse order of construction: the derived class's destructor body runs first, then the derived class's own members are destroyed (in reverse declaration order), and finally the base class subobject is destroyed.
The most-derived destructor runs first, then destruction proceeds outward through each base class in turn — the reverse of construction order.
class Animal
{
public:
~Animal() { std::cout << "Animal destroyed\n"; }
};
class Dog : public Animal
{
public:
~Dog() { std::cout << "Dog destroyed\n"; }
};
{
Dog d;
}
// output:
// Dog destroyed
// Animal destroyedWhy This Order Matters
Construct-base-first, destroy-base-last is what makes it safe for a derived class's constructor body and member functions to assume the base part is already fully formed, and for the base class's own destructor to run without worrying that the derived part has already been torn down out from under it. This is the same underlying discipline as RAII, applied across an entire object hierarchy rather than a single object.
Virtual Destructors: The Basic Rule
If a class is intended to be deleted through a pointer to its base class, the base class's destructor must be declared virtual. Otherwise, deleting through a base pointer only runs the base class's destructor — the derived part of the object is never destroyed, which for a resource-owning derived class means a leak (or worse, undefined behavior).
Marking Animal's destructor virtual ensures that deleting through an Animal* still dispatches to ~Dog() first.
The dynamic-dispatch machinery that makes this work — how a virtual call is resolved at runtime rather than compile time — belongs to the Polymorphism page. This page's concern is narrower: a base class meant to be used through base pointers or references needs a virtual destructor, as a basic rule of hierarchy design, independent of the deeper mechanics of virtual dispatch.
class Animal
{
public:
virtual ~Animal() {} // virtual: safe to delete through Animal*
};
class Dog : public Animal
{
public:
~Dog() override { std::cout << "Dog cleanup\n"; }
};
Animal* a = new Dog();
delete a; // correctly runs ~Dog() then ~Animal(), because ~Animal() is virtualUpcasting Basics
A pointer or reference to a derived class converts implicitly to a pointer or reference to its accessible base class — this is called upcasting, and it requires no cast keyword because it can never fail: every Dog genuinely is an Animal.
ref and ptr both refer to the same Dog object, but viewed through the Animal part of it; only members Animal declares (or that are virtual, per the Polymorphism page) are accessible through that view without an explicit downcast.
This conversion is what lets functions be written to accept Animal& or Animal* and work correctly with any derived type — a Dog, a Cat, or any other class publicly derived from Animal.
Dog d;
Animal& ref = d; // upcast: reference to the Animal part of d
Animal* ptr = &d; // upcast: pointer to the Animal part of d
ref.eat(); // calls Animal's member — visible through the Animal viewObject Slicing
Object slicing occurs when a derived object is copied into a base-class object by value, rather than accessed through a pointer or reference. Only the base part of the object is copied; the derived-specific members are discarded ("sliced off").
printAnimal receives a plain Animal, constructed by copying only the Animal subobject of d; d.breed simply does not exist in the parameter, because Animal has no such member.
Slicing is avoided by taking base-class parameters by reference or pointer (Animal& or Animal*) instead of by value, so the function operates on the original object rather than a freshly sliced copy.
class Animal
{
public:
std::string name = "generic";
};
class Dog : public Animal
{
public:
std::string breed = "terrier";
};
void printAnimal(Animal a) // takes by VALUE
{
std::cout << a.name << '\n';
}
Dog d;
printAnimal(d); // d is sliced: only the Animal part is copied into aHiding Base-Class Members
If a derived class declares a member function with the same name as a base-class member function (even with a different parameter list), the derived class's version hides all base-class overloads of that name by default, rather than overloading alongside them.
Declaring greet() in Derived hides both overloads of greet from Base, not just the one with the matching signature — name lookup finds Derived::greet first and stops there.
class Base
{
public:
void greet() { std::cout << "Base::greet\n"; }
void greet(int times) { std::cout << "Base::greet(int)\n"; }
};
class Derived : public Base
{
public:
void greet() { std::cout << "Derived::greet\n"; }
};
Derived d;
d.greet(); // Derived::greet
// d.greet(5); // error: Base::greet(int) is hidden by Derived::greet()Calling a Hidden or Overridden Base Member Explicitly
A hidden (or, in a polymorphic hierarchy, overridden) base-class member can still be called explicitly by qualifying it with the base class's name and the scope resolution operator.
Base::greet() bypasses ordinary name lookup and calls the base class's version directly, regardless of hiding.
class Derived : public Base
{
public:
void greet()
{
Base::greet(); // explicitly call the hidden base version
Base::greet(3); // the hidden overload is still reachable this way
std::cout << "Derived::greet\n";
}
};Composition vs. Inheritance
Composition models a has-a relationship: a class contains an object of another type as a member, rather than deriving from it. A Car has-an Engine is naturally composition; a Dog is-an Animal is naturally inheritance.
Car does not inherit from Engine; it simply owns one as a member and delegates to it. This avoids exposing Engine's interface as if it were Car's own, and avoids implying Car is-an Engine, which would not be true.
When both relationships seem plausible, composition is generally the safer default: it couples the two classes more loosely, and does not commit to an is-a relationship that may not hold in every respect. Reach for public inheritance specifically when the is-a relationship is genuine and the derived type is meant to be usable wherever the base type is expected.
// Composition: Car HAS an Engine
class Engine { public: void start(); };
class Car
{
public:
void drive() { engine.start(); }
private:
Engine engine;
};Best Practices
Use public inheritance only when an is-a relationship genuinely holds; when the relationship is really "implemented in terms of" rather than "is a kind of," prefer composition, which keeps the two classes more loosely coupled.
Declare a base class's destructor virtual whenever objects of a derived class may be deleted through a pointer or reference to that base class, so the full object is destroyed correctly.
Always initialize a base class explicitly in a derived constructor's initializer list when the base class has no default constructor, or when a specific non-default base constructor should run.
Prefer passing objects that will be used polymorphically by reference or pointer rather than by value, to avoid accidentally slicing a derived object down to its base part.
Keep base-class data members private and expose a protected interface (accessor functions, or a small protected data member set) deliberately, rather than making everything protected by default — protected still exposes implementation details to every present and future derived class.
When hiding a base member's name is unintentional, either rename the derived member or bring the base overloads into scope with a using declaration (using Base::greet;) so both remain callable without qualification.
Complete Example
Construction runs Animal's constructor before Dog's; describe() is an inherited member usable through the Animal* view; bark() requires the Dog-specific view; delete a correctly runs both destructors, in reverse order, because ~Animal() is virtual.
new Dog(...) constructs the Animal part first, printing "Rex (Animal) constructed", then Dog's own part, printing "Labrador (Dog) constructed" a->describe() calls Animal::describe(), which is inherited and visible through Animal* static_cast<Dog*>(a) downcasts back to Dog* so bark(), a Dog-only member, can be called delete a runs ~Dog() first ("Labrador (Dog) destroyed"), then ~Animal() ("Rex (Animal) destroyed"), in that order because ~Animal() is virtual
#include <iostream>
#include <string>
#include <utility>
class Animal
{
public:
Animal(std::string name, int age)
: name(std::move(name)), age(age)
{
std::cout << this->name << " (Animal) constructed\n";
}
virtual ~Animal()
{
std::cout << name << " (Animal) destroyed\n";
}
void describe() const
{
std::cout << name << " is " << age << " years old\n";
}
protected:
std::string name;
int age;
};
class Dog : public Animal
{
public:
Dog(std::string name, int age, std::string breed)
: Animal(std::move(name), age), breed(std::move(breed))
{
std::cout << this->breed << " (Dog) constructed\n";
}
~Dog() override
{
std::cout << breed << " (Dog) destroyed\n";
}
void bark() const
{
std::cout << name << " says woof\n";
}
private:
std::string breed;
};
int main()
{
Animal* a = new Dog("Rex", 3, "Labrador");
a->describe();
Dog* d = static_cast<Dog*>(a);
d->bark();
delete a;
return 0;
}Mental Model
This layered picture is a common implementation intuition for how a derived object relates to its base part; the standard does not mandate a specific memory layout, but this mental model is a reliable way to reason about construction order, destruction order, and what a base pointer can and cannot see directly.
A Dog object in memory conceptually contains its Animal part
as a sub-object, followed by Dog's own members:
+------------------------+
| Animal part | <- constructed first, destroyed last
| (name, age) |
+------------------------+
| Dog's own members | <- constructed second, destroyed first
| (breed) |
+------------------------+
An Animal* or Animal& pointing at this object can only
see the Animal part directly — the Dog part is still there,
but reaching it requires either a virtual call (Polymorphism)
or an explicit downcast.Quick Reference
A condensed summary of the rules covered on this page.
Concept | Key rule
------------------------------+---------------------------------------------
class D : public B | Public inheritance: D is-a B
Access through public inherit.| public stays public, protected stays protected
Construction order | Base first, then derived members, then derived body
Destruction order | Reverse: derived body, derived members, base last
Base init | Name the base in the derived constructor's init list
Virtual destructor | Required if deleting a derived object through Base*
Upcast (Derived* -> Base*) | Implicit, always safe
Downcast (Base* -> Derived*) | Needs an explicit cast, not implicit
Object slicing | Happens when a derived object is copied by value into a base-typed variable
Composition | Has-a relationship: member object, not inheritanceKey Takeaways
Inheritance lets a derived class reuse and extend a base class's members, and public inheritance is the form that models a genuine is-a relationship.
The access specifier used for inheritance (public, protected, or private) controls how the base class's public and protected members are re-exposed through the derived class; a base class's private members are never directly reachable from a derived class.
Construction always runs base-first, derived-second; destruction always runs in the exact reverse order.
A base class meant to be deleted through a base pointer needs a virtual destructor, or the derived part of the object will never be destroyed.
Upcasting from derived to base is implicit and always safe; copying a derived object by value into a base-typed variable causes object slicing, discarding the derived-specific members.
When an is-a relationship does not genuinely hold, composition (a has-a relationship) is usually the better tool than inheritance.
Dynamic dispatch, virtual functions, overriding, and abstract classes build on the mechanics covered here but are taught in depth on the Polymorphism page.
Given class Dog : public Animal, and Animal has a protected member int age and a public member void eat(), what is the accessibility of age and eat() from within Dog's own member functions, and from code entirely outside the Animal/Dog hierarchy?
Within Dog's own member functions, age remains protected (accessible) and eat() remains public (accessible), because public inheritance preserves each member's original accessibility level. From code outside the hierarchy, eat() is still callable (it is public), but age remains inaccessible (it was protected in Animal, and protected members are never accessible from outside the hierarchy regardless of the derived class's own access level).
In what order do a Dog object's Animal part and Dog's own members get constructed, and in what order are they destroyed? Why is the order fixed this way?
Construction: the Animal base subobject is fully constructed first, then Dog's own members are initialized in declaration order, then Dog's constructor body runs. Destruction is the exact reverse: Dog's destructor body runs first, then Dog's own members are destroyed in reverse declaration order, then the Animal base is destroyed last. The order is fixed so that a derived class's members and constructor body can always assume the base part is already valid, and so a base class's destructor never runs while the derived part might still depend on it.
A base class Shape has a non-virtual destructor. Code creates a derived Circle object with new and stores it in a Shape*, then deletes it through that pointer. What goes wrong, and what single change fixes it?
Because ~Shape() is not virtual, delete through a Shape* only calls ~Shape() — Circle's own destructor never runs, so any resources Circle itself owns are never released (and, more generally, this is undefined behavior for polymorphic deletion through a non-virtual base destructor). The fix is to declare Shape's destructor virtual, so deleting through a Shape* correctly runs ~Circle() first, then ~Shape().
Explain object slicing using a function void printShape(Shape s) that takes its parameter by value, called as printShape(circle) where circle is a Circle derived from Shape.
Because printShape takes Shape by value, the argument is copied into a plain Shape object — only the Shape part of circle is copied; any members Circle adds beyond what Shape declares are discarded in the process ('sliced off'). Inside printShape, s behaves as an ordinary Shape with no trace of circle's Circle-specific data, even though the original circle argument was a full Circle.
When should a has-a relationship (composition) be preferred over public inheritance (an is-a relationship) between two classes?
Composition should be preferred whenever the is-a relationship does not genuinely hold — when one class merely uses or is implemented in terms of another rather than being a specialized kind of it. Composition couples the two classes more loosely (the containing class exposes only what it chooses to, through its own interface) and does not force every capability of the contained type onto the containing type's public interface.
What does this program print, in order, and why?
Construction runs base-first: "Base " then "Derived " as d is constructed. When the inner block ends, destruction runs in reverse: "~Derived " then "~Base ". Output: Base Derived ~Derived ~Base.
Write a base class Shape with a protected std::string name member, a constructor taking the name, a virtual destructor, and a describe() member printing the name. Derive a class Circle from Shape with an additional double radius member, a constructor that initializes both the Shape base and radius, and its own describe() that calls Shape::describe() explicitly and then prints the radius. Demonstrate creating a Circle through a Shape* and calling describe() through Base::describe() explicitly from within Circle::describe().
Circle's constructor initializes its Shape base with the name, then its own radius member. c.describe() calls Circle::describe(), which first calls Shape::describe() explicitly (printing "Shape: MyCircle"), then prints "Radius: 2.5" itself. Note that Circle::describe() hides Shape::describe() by name, which is why the explicit Shape:: qualification is needed to reach the base version.
What does this program print, and what phenomenon explains why it does not print "meow"?
It prints "...". makeSound takes its parameter by value as a plain Animal, so passing c constructs a new Animal by copying only the Animal part of c — this is object slicing. Cat's own sound member ("meow") is a separate member that shadows Animal's sound within a Cat object, but it is not part of the Animal subobject, so it is discarded when c is sliced down to an Animal. The printed a.sound is Animal's own "..." member, unaffected by Cat's separately-declared sound.
Write a base class Employee with a protected std::string name and double baseSalary, a constructor taking both, and a virtual double totalPay() const returning baseSalary. Derive Manager from Employee, adding a double bonus member and a constructor taking name, baseSalary, and bonus that initializes the Employee base and bonus. Manager does not need to override totalPay() for this exercise — just demonstrate constructing a Manager and calling totalPay() (inherited from Employee) as well as a Manager-only member function bonusAmount() returning bonus.
Manager's constructor initializes its Employee base with name and baseSalary, then its own bonus member. m.totalPay() is inherited unchanged from Employee (Manager does not override it in this exercise) and returns baseSalary, 60000. m.bonusAmount() is a Manager-only member returning bonus, 5000. Output: 60000 5000.
class Base
{
public:
Base() { std::cout << "Base "; }
~Base() { std::cout << "~Base "; }
};
class Derived : public Base
{
public:
Derived() { std::cout << "Derived "; }
~Derived() { std::cout << "~Derived "; }
};
int main()
{
{
Derived d;
}
return 0;
}class Animal
{
public:
std::string sound = "...";
};
class Cat : public Animal
{
public:
std::string sound = "meow";
};
void makeSound(Animal a)
{
std::cout << a.sound << '\n';
}
int main()
{
Cat c;
makeSound(c);
return 0;
}