Hello! Welcome to your eighth and final lesson in the "C++ Object-Oriented Fundamentals" module.
Introduction
In our last lesson, we mastered the const keyword, learning how to write safer, more robust code by making promises to the compiler about what shouldn't change. This practice, known as "const correctness," is a key part of professional C++ development.
Today, we'll cover the final pillar of object-oriented programming in this module: inheritance. The learning outcome for this lesson is to implement basic inheritance to create derived classes from base classes.
Inheritance is a mechanism that allows you to create a new class—the derived class—that reuses, extends, and modifies the behavior of an existing class—the base class. This is fundamental for two main reasons:
- Code Reusability: It helps you avoid duplicating code for common functionalities.
- Modeling Relationships: It allows you to model "is-a" relationships (e.g., a
Caris aVehicle, aTemperatureSensoris aSensor), leading to more logical and organized code.
This concept is especially powerful in embedded systems for creating structured and reusable drivers and tasks, as we'll see with an ESP32 example.
1. The "Why": Avoiding Code Duplication
Imagine you have a class to represent a menu item at a restaurant. It has a name and calories. Now, you want to represent a drink, which is also a menu item but has an additional property: volume (in ounces). Without inheritance, you might be tempted to copy-paste the MenuItem class and add the new property. This leads to duplicated code, which is a maintenance nightmare. If you need to change how menu items are printed, you'd have to update it in multiple places.
Inheritance solves this elegantly.
Introduction To Inheritance | C++ Tutorial
This video from Portfolio Courses does an excellent job of first demonstrating the problem of code duplication and then introducing inheritance as the solution.
Please watch from the beginning to 07:48. Pay attention to: The initial setup with two separate, duplicated classes (MenuItem, Drink). The problem this causes when a change is needed. How the syntax class Drink : public MenuItem solves this by making Drink inherit from MenuItem.
2. The "How": Syntax and Implementation
As you saw in the video, the syntax for inheritance in C++ is straightforward.
class BaseClass {
// ... members ...
};
class DerivedClass : public BaseClass {
// ... additional members specific to DerivedClass ...
};
- Base Class: The class being inherited from (
MenuItem). Also called a parent or superclass. - Derived Class: The class that inherits (
Drink). Also called a child or subclass. publicInheritance: This is the most common form. It means thatpublicmembers of theBaseClassremainpublicin theDerivedClass.
An object of the DerivedClass now has access to all the public members of the BaseClass, in addition to its own.
Inheritance in C++ for Beginners
This article from Medium provides a concise summary of the core concepts and syntax. It's a good, quick read to solidify your understanding.
Read the sections "What is the purpose of inheritance?", "Basic syntax of inheritance", and "Simple example of inheritance". This will reinforce the syntax and show you another clear example (Employee and Teacher).
3. Constructors and protected Access
Two important details arise when using inheritance: how constructors work, and how to grant special access to derived classes.
Constructors in an Inheritance Hierarchy
When you create an object of a derived class, the base class part of it must be constructed first. This means the base class's constructor must be called. If the base class has a constructor that requires arguments, you must explicitly call it from the derived class's constructor using an initializer list.
The protected Access Specifier
So far, we've used public (accessible everywhere) and private (accessible only within the class). Inheritance introduces a third specifier: protected.
protectedmembers are likeprivatemembers, but with one exception: they are also accessible within any derived classes.
This is useful when you want to hide implementation details from the outside world but allow derived classes to access and use them.
C++ OOP - What is inheritance in programming?
This video from CodeBeauty provides a detailed walkthrough of creating a derived class, focusing on the crucial topics of constructor chaining and the protected keyword.
Watch the following segments carefully: Defining a Derived Class (02:15 - 03:45): See the syntax in action. Derived Class Constructor (03:45 - 06:40): This is a key part. Notice how the derived constructor calls the base constructor (YouTubeChannel(...)) in the initializer list. Accessing Base Members (06:40 - 08:42): Confirms that the derived object can use the base class's public methods. Adding Unique Members (08:42 - 12:24): Shows how to extend the derived class with its own specific functionality. protected Access (13:19 - 16:16): A great explanation of why protected is needed and how it allows derived classes to access base class members that should otherwise be private.
4. A Practical ESP32 Example: The Task Abstraction
Now, let's tie this directly to your goal of C++ for ESP32. A common and powerful pattern in embedded programming with an OS like FreeRTOS (which the ESP-IDF uses) is to abstract away the boilerplate for creating tasks. We can create a base Task class that handles the FreeRTOS API calls and then derive specific tasks from it.
The base Task class will manage the name, stack size, priority, and the call to xTaskCreate. The specific work that each task does will be implemented in a run() method in the derived classes.
To make this work, the base class declares the run() method as a pure virtual function:
virtual void run() = 0;
virtual: This keyword signals that a derived class can provide its own implementation for this function.= 0: This makes it a pure virtual function. It means the base class provides no implementation; a derived class must implement it. A class with one or more pure virtual functions is called an abstract base class—you cannot create an object of it directly. It only serves as a template for derived classes.
When a derived class provides its implementation, it should use the override keyword to be explicit and allow the compiler to check that it is indeed overriding a base class function.
Learn C++ Inheritance with ESP32 | A Powerful Task Setup
The following article from SaludPCB demonstrates this exact pattern. We will focus only on the code for the base and derived classes.
You don't need to read the whole article. Instead, carefully study the two main code blocks provided: First, find the section "Setting Up the Base Task Class" and examine the code for the Task class. Note the constructor and the virtual void run() = 0; line. Next, scroll down to the code block under "Bringing It All Together in app_main". Examine how CounterTask, TemperatureTask, and HumidityTask are defined. Notice the : public Task syntax and how each one provides its own run() override method.
This Task inheritance pattern is extremely useful. It separates the generic logic of task creation (in the base class) from the specific application logic (in the derived classes), making your code much cleaner, more organized, and easier to manage as your projects grow in complexity.
Conclusion
Congratulations on completing the first module! You've built a solid foundation in C++'s object-oriented features. Inheritance is a powerful tool that, when used correctly, promotes code reuse and logical design.
Key Takeaways:
- Inheritance allows a derived class to inherit the members of a base class, modeling an "is-a" relationship.
- The primary syntax is
class Derived : public Base. - It's a powerful tool for code reuse, preventing duplication and simplifying maintenance.
- A derived class constructor must call the base class constructor, often done via the initializer list.
- The
protectedaccess specifier allows base class members to be accessed by derived classes but keeps them hidden from the public. - Abstract base classes with pure virtual functions (
virtual void func() = 0;) can define an interface that all derived classes must implement.
This lesson concludes our deep dive into C++ fundamentals. In our next lesson, we will begin Module 2: ESP32 Platform and Development Environment. We'll shift our focus to the hardware itself, starting with the ESP32's architecture, and then move on to setting up the toolchain you'll need to build and run the C++ code you've been learning.