18. Inheritance in C++#
With inheritance, we can create a new class by extending an existing class. The new class inherits the existing class’s attributes and methods (the members). We can then add attributes and/or methods. We can also modify existing attributes and methods to change functionality.
As we look around the world, we can find many instances of hierarchy in which specialized classes exist. For example, the animal taxonomy serves to classify animals into different classes (taxonomic ranks based upon the different attributes that animals possess. Planes, cars, and trains are specialized cases of vehicles. Circles, triangles, and rectangles are specialized cases of shapes. You can also view these examples using the phrase “is-a”. A train is a vehicle. A circle is a shape. A dog is a mammal.
In object-oriented programming, inheritance creates this “is-a” relationship among classes. We build a new class from an existing class. The existing (original) class is called a parent, superclass, or base class. The superclass is the more general class. The new class is called a child, subclass, or derived class. This new class is the more specialized class. The subclass becomes specialized by adding attributes and/or methods. The subclass can also become more specialized by modifying the existing state or behavior.
The following table demonstrates possibilities when creating an inheritance hierarchy. The first row reuses the classes used in the Python inheritance notebook. As you look through the table, think about what generalized behavior and state could be placed in the base class versus specialized behavior and state in the derived classes.
Base Class |
Derived Classes |
|---|---|
Employee |
SalariedEmployee, HourlyEmployee, CommissionedEmployee |
Vehicle |
Train, Ship, Bicycle, Automobile |
FinancialInstrument |
Bond, Stock, Derivative, Option |
Loan |
PersonalLoan, Mortgage, AutoLoan, StudentLoan |
Person |
Student, Undergraduate, Graduate, Faculty, Staff, Alumnus, Visitor |
Consider building a system to manage a library. One aspect that we’ll need to track such a system is the different assets that the library may own (or, in the case of digital assets, lease). One such set of classes may appear in the following UML diagram.

This system has an abstract base class of LibraryItem that contains general properties that all assets possess as well as an abstract function to displays details about itself. From design perspective, abstract classes represent generalized concepts or things. By declaring a class abstract, we prevent any objects of this type from being directly instantiated. By placing the class higher in the inheritance hierarchy, we can provide common implementation details that any of our subclasses will possess. These details are both state/attributes - member variables as well as behavior - functions. We also define three additional abstract classes: Book, Movie, and DigitalAsset. In the cases of DigitalAsset, the intention is use this class as an interface. Classes that inherit from this class must set the attributes for fileFormat and fileSize. We then provide five concrete classes: PrintedBook, EBook, DigitalMovie, BlueRay, and DVD.
18.1. Syntax#
To inherit from a base class in C++, add a colon and then the class name after the name of the class being created.
class Base {
public:
void showBase() {
cout << "This is base class." << endl;
}
};
class Derived : [visibilityModify] Base {
public:
void showDerived() {
cout << "This is derived class." << endl;
}
};
The visibility modifier allows us to adjust the visibility of the base members in the derived class. Not including this modifier keeps the visibility at their already defined levels. Not that you can only make something less “visible”, you can’t make a member more visible:
Public Inheritance:
publicPublic members of the base class become public in the derived class, and protected members of the base class become protected in the derived class.Protected Inheritance:
protectedBoth public and protected members of the base class become protected in the derived class.Private Inheritance:
privateBoth public and protected members of the base class become private in the derived class.
18.2. Constructors and Destructors#
The derived class does not inherit the base class’s constructors and destructors. However, when an object of the derived class is created, the base class’s constructor is automatically called first, followed by the derived class’s constructor. Similarly, the derived class’s destructor is called first, followed by the base class’s destructor. Note the ordering of these two operations - we create from the top downwards and destroy from the bottom upwards. Within a constructor’s initializer list, we can explicitly call the parent’s constructor - useful when a class has more than one constructor and we do not want to rely upon the behavior in the default constructors (if it even exists).
18.3. protected#
protected is an access modifier that makes members accessible in derived classes and not accessible by the code that is outside the class (or outside the derived class). Useful when you want derived classes to have access but to keep the member private from other parts of your program.
18.4. Function Overriding#
In the derived class, you can provide a definition for one of the base class’s function members. This process is called function overriding. The overridden function in the derived class should have the same name, return type, and parameters as the function in the base class.
In the following example, the Derived class overrides show() from the Base class. The keyword override, while optional, is used to make the intention clear to readers. We also have the method explicitly call the method in the base class to demonstrate chaining the overridden method. Note that the Another does not do so.
//filename: override.cpp
//complile: g++ -std=c++17 -o override override.cpp
//execute: ./override
#include<iostream>
using namespace std;
class Base {
public:
virtual void show() {
cout << "This is base class.\n";
}
};
class Derived : public Base {
public:
void show() override {
Base::show();
cout << "This is derived class.\n";
}
};
class Another : public Base {
public:
void show() override {
cout << "This is another class.\n";
}
};
int main() {
Derived d;
d.show();
Another a;
a.show();
}
18.5. Polymorphism#
Polymorphism is the ability to send a message (call a method/function) to an object at runtime, without knowing the exact type of the receiving object. Within C++, you’ll need to add the keyword virtual to a function to enable run-time polymorphism. virtual informs the compiler to perform a dynamic linkage at runtime on the function to ensure that the correct function is called for an object, regardless of the reference or pointer type used to call the function. Virtual functions have an extremely slight performance penalty as they use a lookup table to determine which function to call at runtime. However, this overhead is negligible in most applications compared to the flexibility and benefits that virtual functions provide. See displayDetails() in the Case Stude Code section.
A pure virtual function is a virtual function for which we don’t have an implementation and set it to 0. Classes containing pure virtual functions are termed abstract, and you cannot instantiate an object of an abstract class.
18.6. Case Study Code#
//filename: librarysystem.hpp
//complile: g++ -std=c++17 -o librarysystem librarysystem.hpp
//execute: ./librarysystem
#ifndef LIBRARYSYSTEM_H
#define LIBRARYSYSTEM_H
#include <string>
class LibraryItem {
public:
LibraryItem(const std::string& title, const std::string& authorship, int publicationYear)
: title(title), authorship(authorship), publicationYear(publicationYear) {}
virtual void displayDetails() const = 0;
protected:
std::string title;
std::string authorship;
int publicationYear;
virtual ~LibraryItem() = 0;
};
LibraryItem::~LibraryItem() {}
// Abstract class for digital assets
class DigitalAsset {
public:
DigitalAsset(double fileSize, const std::string& fileFormat)
: fileSize(fileSize), fileFormat(fileFormat) {}
void displayDetails() const {
std::cout << "File Size (MB): " << fileSize <<"\n";
std::cout << "File Format: " << fileFormat << "\n";
}
protected: // Make protected to allow derived classes access
double fileSize;
std::string fileFormat;
virtual ~DigitalAsset() = 0;
};
DigitalAsset::~DigitalAsset() {}
// Derived class for books
class Book : public LibraryItem {
public:
Book(const std::string& title, const std::string& author, int publicationYear,
const std::string& genre, int pageNumber)
: LibraryItem(title, author, publicationYear), genre(genre), pageNumber(pageNumber) {}
void displayDetails() const {
std::cout << "Book Title: " << title << "\n";
std::cout << "Author: " << authorship << "\n";
std::cout << "Year: " << publicationYear << ", Genre: " << genre << ", Pages:" << pageNumber << "\n";
}
private:
std::string genre;
int pageNumber;
protected:
virtual ~Book() = 0;
};
Book::~Book() {}
class Movie : public LibraryItem {
public:
Movie(const std::string& title, const std::string& director, int publicationYear,
const std::string& genre, int duration)
: LibraryItem(title, director, publicationYear), genre(genre), duration(duration) {}
void displayDetails() const {
std::cout << "Movie: " << title << "\n";
std::cout << "Directory: " << authorship << "\n";
std::cout << "Year Released: " << publicationYear << ", Genre: " << genre << ", Duration(min):" << duration << "\n";
}
private:
std::string genre;
int duration;
protected:
virtual ~Movie() = 0;
};
Movie::~Movie() {}
class EBook : public Book, public DigitalAsset {
public:
EBook(const std::string& title, const std::string& author, int publicationYear,
const std::string& genre, int pageNumber,
double fileSize, const std::string& fileFormat)
: Book(title, author, publicationYear, genre, pageNumber),
DigitalAsset(fileSize, fileFormat) {}
~EBook() {}
void displayDetails() const {
Book::displayDetails();
DigitalAsset::displayDetails();
}
};
class PrintedBook : public Book {
public:
PrintedBook(const std::string& title, const std::string& author, int publicationYear,
const std::string& genre, int pageNumber,
const std::string& coverType, double weight)
: Book(title, author, publicationYear, genre, pageNumber),
coverType(coverType), weight(weight) {}
~PrintedBook() {}
void displayDetails() const {
Book::displayDetails();
std::cout << "CoverType: " << coverType << "\n";
}
private:
std::string coverType;
double weight;
};
class DigitalMovie : public Movie, public DigitalAsset {
public:
DigitalMovie(const std::string& title, const std::string& director, int publicationYear,
const std::string& genre, int duration,
double fileSize, const std::string& fileFormat)
: Movie(title, director, publicationYear, genre, duration),
DigitalAsset(fileSize, fileFormat) {}
~DigitalMovie() {}
void displayDetails() const {
Movie::displayDetails();
DigitalAsset::displayDetails();
}
};
class BluRay : public Movie {
public:
BluRay(const std::string& title, const std::string& director, int publicationYear,
const std::string& genre, int duration,
const std::string& resolution)
: Movie(title, director, publicationYear, genre, duration), resolution(resolution) {}
~BluRay() {}
void displayDetails() const {
Movie::displayDetails();
std::cout << "Resolution: " << resolution << "\n";
}
private:
std::string resolution;
};
class DVD : public Movie {
public:
DVD(const std::string& title, const std::string& director, int publicationYear,
const std::string& genre, int duration, int regionCode)
: Movie(title, director, publicationYear, genre, duration), regionCode(regionCode) {}
~DVD() {}
void displayDetails() const {
Movie::displayDetails();
std::cout << "RegionCode: " << regionCode << "\n";
}
private:
int regionCode;
};
#endif // LIBRARYSYSTEM_H
Note: LibaryItem has this declaration: virtual void displayDetails() = 0;. By using both the keyword virtual and assigning 0 to the function, displayDetails is a pure virtual function. Such a function has no implementation in the base class and must be overridden in any derived classes (unless those clases are abstract as well).
We also want Book and Movie to be abstract, but those classes do not have any defined behavior. As a workaround, we declare the destructor as pure virtual in those classes. However, we must then implement those destructors (even if they are empty).
Virtual Destructors: If you’re dealing with inheritance, it’s often advisable to make your base class destructor virtual`. This ensures that the correct derived class destructor is called when an object is destroyed through a base pointer.
In the following code block, we instantiate several objects of the various classes and place them all into a container. As they are derived from the base class of, we can simply treat them as such. However, that also means that we are limited to accessing the behavior and state of just LibraryItem when using that type. As you can see in the for-each loop, we use polymorphism to call the appropriate displayDetails method for each object.
//filename: library.cpp
//complile: g++ -std=c++17 -o library library.cpp
//execute: ./library
#include <iostream>
#include "librarysystem.hpp"
#include <vector>
int main() {
EBook ebook("The Joy of Programming", "John Slankas", 2023, "Technology", 500, 2.5, "ePub");
PrintedBook printedBook("Introducing Python, 2nd Edition", "Bill Lubanvoic", 2019, "Technology", 627, "Softcover", 2.35);
DigitalMovie digitalMovie("The Shawshank Redemption", "Frank Darabont", 1994, "Drama", 144, 1.5, "MP4");
BluRay bluRay("The Godfather", "Francis Ford Coppola", 1972, "Drama", 195, "1080p");
DVD dvd("The Matrix", "Lana Wachowski, Lilly Wachowski", 1999, "Action", 156, 2);
std::vector<LibraryItem*> libraryItems;
libraryItems.push_back(&ebook);
libraryItems.push_back(&printedBook);
libraryItems.push_back(&digitalMovie);
libraryItems.push_back(&bluRay);
libraryItems.push_back(&dvd);
for (const auto& item : libraryItems) {
item->displayDetails();
std::cout << "---------------------------" << "\n";
}
return EXIT_SUCCESS;
}