Introduction
Hello! In our last lesson, we mastered const-correctness, a crucial practice for writing safe and self-documenting code. You now have a solid foundation in designing individual classes with well-defined interfaces and behaviors.
This lesson marks our entry into the next major pillar of Object-Oriented Programming (OOP): Inheritance. Inheritance is the mechanism that allows us to create new classes that are based on existing ones, inheriting their attributes and behaviors. This enables you to build hierarchies of related classes, promote code reuse, and model real-world relationships in your software.
Our focus today will be on implementing the most common type of relationship: the "is-a" relationship, using public inheritance. By the end of this lesson, you'll be able to define a new class that extends a base class, understand how member access works across this hierarchy, and appreciate when to use inheritance in your designs.
1. What is Inheritance? The "Is-A" Relationship
At its core, inheritance allows you to define a general base class (also called a parent or superclass) that contains common functionality, and then create more specific derived classes (also called child or subclasses) that inherit this functionality and add their own unique features.
The primary benefit, as with many programming constructs, is avoiding code duplication. If multiple classes share common features, you can place those features in a base class and have the other classes inherit from it.
The relationship formed by public inheritance is called an "is-a" relationship. For example, a Dog is an Animal. A Car is a Vehicle. A Student is a Person. The derived class is a specialized version of the base class.
This image provides a simple, clear visualization of this concept.

To start, let's watch a short video that explains this core idea.
This clip from The Cherno introduces the concept of inheritance and its primary motivation: avoiding code duplication by creating class hierarchies.
Watch the first 1 minute and 18 seconds of the video. Focus on the explanation of a base class containing common functionality and subclasses branching off from it.
"Is-A" vs. "Has-A"
It's crucial to distinguish the "is-a" relationship from another common one: the "has-a" relationship (also known as composition).
- Is-A (Inheritance): A
Studentis aPerson. You use public inheritance. - Has-A (Composition): A
Carhas aEngine. TheCarclass would contain anEngineobject as a member variable, but aCaris not a type ofEngine.
Making this distinction is a fundamental part of object-oriented design. The following reading explains this difference clearly.
This article from Runestone Academy defines the 'IS-A Relationship' and contrasts it with the 'HAS-A Relationship', which is a critical design concept.
First, read the introductory paragraphs of the article, down to where it says 'Logic Gates and Circuits'. Then, skip down to the subsection 'Building Circuits' and read the three paragraphs that explicitly define and contrast the 'IS-A' and 'HAS-A' relationships.
2. Implementing Public Inheritance in C++
Now that we understand the concept, let's see how to implement it. The syntax is straightforward: after the derived class name, you use a colon, the public keyword, and the name of the base class.
class Base {
// ... base class members ...
};
class Derived : public Base {
// ... derived class members ...
};
Let's return to the video from The Cherno, which provides a perfect demonstration. It starts by showing two classes with duplicated code and then refactors them to use inheritance, making the code much cleaner.
This segment demonstrates the syntax for public inheritance and shows how it eliminates redundant code.
Watch from 01:18 to 04:03. Observe how the Player class is changed from a standalone class with copied code to a class that inherits from Entity using the : public Entity syntax. Notice his point that the Player class now is an Entity and contains all its members.
When Player inherits from Entity, it gets all of Entity's members. The Player object will contain the x and y member variables from Entity in addition to its own name member.
3. Inheritance and Access Specifiers
How a derived class can access members of its base class is governed by access specifiers: public, private, and a new one, protected.
public: Accessible by anyone.private: Accessible only by members (and friends) of the same class. Derived classes cannot access private members of the base class.protected: Accessible by members of the same class, its friends, and its derived classes. It is inaccessible from outside the class hierarchy.
The article "Inheritance and access specifiers" from Learn C++ is an excellent resource for this topic.
24.5 — Inheritance and access specifiers
This reading will explain the protected access specifier and detail how member access works under public inheritance.
Read the following sections from the article: The introduction, which recaps public and private. 'The protected access specifier', which explains this new keyword. 'Public inheritance', which is the most important part and directly addresses our learning goal. Pay close attention to the table showing what happens to inherited members.
Here is a summary of how public inheritance affects the access level of inherited members in the derived class:
| Access specifier in Base Class | Resulting Access in Derived Class |
|---|---|
public |
public |
protected |
protected |
private |
Inaccessible |
This means that with public inheritance, the public interface of the base class remains public in the derived class, which is exactly what we want for modeling an "is-a" relationship.
4. Under the Hood: The Memory Layout of Inheritance
Given your interest in how things work at a lower level, let's explore what inheritance means for an object's memory layout. When you create an object of a derived class, the compiler typically lays it out in memory by placing the data members of the base class first, followed by the data members of the derived class.
Conceptual memory layout of a Student object, which inherits from Person. The Person part comes first.
This memory layout is precisely what makes the "is-a" relationship work. Since a Student object literally contains a Person object at its beginning, a pointer to a Student (Student*) can be safely and implicitly converted to a pointer to a Person (Person*)—the address is the same.
The following video demonstrates this principle by simulating C++ inheritance using only C structs and function pointers. This provides a clear mechanical model of what the C++ compiler does for you automatically.
C++ Inheritance Explained: V-Tables, Virtual Destructors & Edge Cases
This clip from DeepDiveDev provides a fantastic 'under the hood' look at inheritance by simulating it in C. It reveals the memory layout that makes the 'is-a' relationship possible.
Watch from 07:15 to 08:11. The key insight is how embedding the 'base' struct as the first member of the 'derived' struct guarantees that their memory addresses are identical, allowing for safe pointer casting—the C equivalent of an 'is-a' relationship.
5. Hands-On Exercise: Creating a Class Hierarchy
Let's apply these concepts. You will create a simple hierarchy of Shape classes.
Instructions:
- Define a
Shapebase class in a file namedShape.hpp.- It should have a
protectedmember:std::string m_name. - It should have a
publicconstructor that takes aconst std::string& nameto initializem_name. - It should have a
public constmember functionstd::string getName() constthat returns the name. - It should have a
public constmember functionvoid print() constthat prints "I am a [name]".
- It should have a
- Define a
Circlederived class in a file namedCircle.hpp.- It should publicly inherit from
Shape. - It should have a
privatemember:double m_radius. - Its
publicconstructor should take adouble radiusand initialize the base class part with the name "Circle" and also initialize its ownm_radiusmember. The syntax for calling the base class constructor isCircle(double radius) : Shape("Circle"), m_radius(radius) {}. - It should have a
public constmember functiondouble getArea() constthat calculates and returns the area (). You can use3.14159for .
- It should publicly inherit from
- In
main.cpp, includeCircle.hpp, create aCircleobject, and test its functionality.- Call the
print()method (inherited fromShape). - Call the
getName()method (inherited fromShape). - Call the
getArea()method (specific toCircle). - Print the results to the console.
- Call the
Starter Code (main.cpp):
#include <iostream>
#include "Circle.hpp" // Make sure to create this file and Shape.hpp
int main() {
// 1. Create a Circle object with a radius of 5.0
Circle myCircle(5.0);
// 2. Call the inherited print() method
std::cout << "Calling print(): ";
myCircle.print();
std::cout << std::endl;
// 3. Call the inherited getName() method
std::cout << "Name of the shape: " << myCircle.getName() << std::endl;
// 4. Call the Circle-specific getArea() method
std::cout << "Area of the circle: " << myCircle.getArea() << std::endl;
return 0;
}
Solution
Shape.hpp
#pragma once // Header guard
#include <string>
#include <iostream>
class Shape {
protected:
std::string m_name;
public:
// Constructor to initialize the name
Shape(const std::string& name) : m_name(name) {}
// Public getter for the name
std::string getName() const {
return m_name;
}
// Public print method
void print() const {
std::cout << "I am a " << m_name;
}
};
Circle.hpp
#pragma once // Header guard
#include "Shape.hpp"
class Circle : public Shape {
private:
double m_radius;
public:
// Constructor calls the base class constructor using an initializer list
Circle(double radius) : Shape("Circle"), m_radius(radius) {}
// Circle-specific method to calculate area
double getArea() const {
return 3.14159 * m_radius * m_radius;
}
};
Expected Output of main.cpp:
Calling print(): I am a Circle
Name of the shape: Circle
Area of the circle: 78.5397
Conclusion
Congratulations! You've just taken your first and most important step into inheritance with C++. You can now build class hierarchies that effectively model "is-a" relationships, promoting cleaner, more maintainable, and reusable code.
Key Takeaways:
- Inheritance models an "is-a" relationship where a derived class is a specialized version of a base class.
- Public inheritance (
class Derived : public Base) is used to create this relationship. - A derived class inherits all members from its base, but can only directly access the
publicandprotectedones.privatebase members are inaccessible. - The
protectedaccess specifier allows access within a class and by its derived classes, but not by the general public. - An object of a derived class physically contains its base class's members, which enables a derived class pointer to be treated as a base class pointer.
Next Up
Now that you can build class hierarchies, you might wonder: what's the most powerful thing I can do with them? The answer is polymorphism, the second major concept in this module. In the next lesson, you'll learn how to use virtual functions to treat objects of different derived classes in a uniform way through a single base class interface. This will unlock a new level of flexibility and power in your C++ programs.