Lesson 3
Understanding Inheritance in C++
Lesson Introduction

Hello there! Welcome to our lesson on understanding inheritance in C++. Today, we're going to explore an important concept in programming called inheritance.

Why Inheritance?

Kids inherit certain traits from their parents, like eye color or hair color. In programming, inheritance works in a similar way. It allows one class (the child class) to inherit properties and behaviors from another class (the parent class). This helps us write more efficient and organized code by reusing existing code.

By the end of this lesson, you will understand what inheritance is in C++, how it works, and how to use it to create organized and reusable code.

Inheritance Syntax and Basic Example: part 1

In C++, we use the : symbol to indicate inheritance. For now, we'll use the public keyword to specify public inheritance. We'll discuss other inheritance options in the next lesson. Let's look at a basic example:

C++
1#include <iostream> 2 3class Cat { 4public: 5 void voice() { 6 std::cout << "Meow!" << std::endl; 7 } 8}; 9 10class Lion : public Cat { 11}; 12 13class Tiger : public Cat { 14};

In this example:

  • Cat is the base class.
  • Lion and Tiger are derived classes inheriting from Cat.
  • Both Lion and Tiger can use the voice() method from their parent class Cat.

Let's take a look at the main function example:

C++
1int main() { 2 Cat cat; 3 Lion lion; 4 Tiger tiger; 5 6 cat.voice(); // Output: Meow! 7 lion.voice(); // Output: Meow! 8 tiger.voice(); // Output: Meow! 9 10 return 0; 11}

In Lion and Tiger, we don't have to define the voice() method; they have it by default because they are inherited from the Cat class.

Inheritance Syntax and Basic Example: part 2

Let's add some attributes to our base class:

C++
1#include <iostream> 2 3class Cat { 4public: 5 std::string color; 6 int age; 7 8 void voice() { 9 std::cout << "Meow!" << std::endl; 10 } 11}; 12 13class Lion : public Cat { 14}; 15 16class Tiger : public Cat { 17};

Now, Cat is defined by its color and age. These attributes are also derived by the Lion and the Tiger classes.

Let's see how we can use them:

C++
1int main() { 2 Lion lion; 3 Tiger tiger; 4 5 lion.color = "Golden"; 6 lion.age = 5; 7 8 tiger.color = "Orange"; 9 tiger.age = 3; 10 11 std::cout << "Lion: " << lion.color << ", " << lion.age << " years old" << std::endl; // Output: Lion: Golden, 5 years old 12 std::cout << "Tiger: " << tiger.color << ", " << tiger.age << " years old" << std::endl; // Output: Tiger: Orange, 3 years old 13 14 return 0; 15}

Both Lion and Tiger can access the color and age attributes inherited from the Cat class.

Overriding and Extending Base Class Functions: Part 1

Sometimes, we need to change the behavior of a base class method in a derived class. This is known as function overriding. To do this, we must mark the method in the base class as virtual and use the override keyword in the derived class. Using override helps to avoid errors by ensuring that the derived class method is actually overriding a base class method.

Example:

C++
1#include <iostream> 2 3class Cat { 4public: 5 virtual void voice() { 6 std::cout << "Meow!" << std::endl; 7 } 8}; 9 10class Lion : public Cat { 11public: 12 void voice() override { 13 std::cout << "Roar!" << std::endl; 14 } 15}; 16 17class Tiger : public Cat { 18public: 19 void voice() override { 20 std::cout << "Grr!" << std::endl; 21 } 22};

Tiger and Lion classes redefine the voice method to have their own unique behavior.

Overriding and Extending Base Class Functions: Part 2

Now, each type of cat has a unique voice:

C++
1int main() { 2 Cat cat; 3 Lion lion; 4 Tiger tiger; 5 6 cat.voice(); // Output: Meow! 7 lion.voice(); // Output: Roar! 8 tiger.voice(); // Output: Grr! 9 10 return 0; 11}
Extending Base Class with Unique Methods: Part 1

In some cases, we may need to add methods to a derived class that are not present in the base class. This way, we can extend the base class' functionality. This can be done simply by defining the new method in the derived class. Let's add a unique method to the Lion class:

C++
1#include <iostream> 2 3class Cat { 4public: 5 virtual void voice() { 6 std::cout << "Meow!" << std::endl; 7 } 8}; 9 10class Lion : public Cat { 11public: 12 void voice() override { 13 std::cout << "Roar!" << std::endl; 14 } 15 16 void hunt() { 17 std::cout << "Lion is hunting!" << std::endl; 18 } 19};

In this example:

  • The Lion class has a unique method called hunt().
  • This method is not available in the Cat class.
Extending Base Class with Unique Methods: Part 2

Let's see how it works:

C++
1int main() { 2 Cat cat; 3 Lion lion; 4 5 cat.voice(); // Output: Meow! 6 lion.voice(); // Output: Roar! 7 lion.hunt(); // Output: Lion is hunting! 8 // cat.hunt(); // Uncommenting this line will cause a compilation error as 'Cat' doesn't have a 'hunt' method 9 10 return 0; 11}

Note:

  • When calling lion.hunt(), we get the output specific to the Lion class, showing how to extend the base class functionality with new methods.
  • We can't call cat.hunt(), because hunt() is the new method of the extended lion class.
Constructors with Inheritance: part 1

The base class constructor is called first when a derived class is instantiated. Let's see how constructors work in inheritance:

C++
1#include <iostream> 2 3class Cat { 4public: 5 std::string color; 6 int age; 7 8 Cat(std::string c, int a) : color(c), age(a) { 9 std::cout << "Cat constructor called" << std::endl; 10 } 11 12 virtual void voice() { 13 std::cout << "Meow!" << std::endl; 14 } 15}; 16 17class Lion : public Cat { 18public: 19 Lion(std::string c, int a) : Cat(c, a) { 20 std::cout << "Lion constructor called" << std::endl; 21 } 22 23 void voice() override { 24 std::cout << "Roar!" << std::endl; 25 } 26};

In this example:

  • The Cat class has a constructor that initializes the color and age attributes.
  • The Lion class inherits from Cat and also has its own constructor.
  • The Lion constructor calls the Cat constructor using the initializer list syntax.
Constructors with Inheritance: part 2

Let's examine our constructors:

C++
1int main() { 2 Lion lion("Golden", 5); 3 4 // Output: 5 // Cat constructor called 6 // Lion constructor called 7 8 std::cout << "Lion: " << lion.color << ", " << lion.age << " years old" << std::endl; // Output: Lion: Golden, 5 years old 9 lion.voice(); // Output: Roar! 10 11 return 0; 12}

When an object of the Lion class is created, the Cat constructor is called first, followed by the Lion constructor.

By calling the base class constructor from the derived class constructor, we ensure the base class attributes are properly initialized.

Destructors in Inheritance: Part 1

Just as constructors are called in a specific order in an inheritance hierarchy, so are destructors. When a derived class object is destroyed, the derived class destructor is called first, followed by the base class destructor. This ensures that any additional cleanup required by derived classes is performed before the base class destructor is executed.

Example:

C++
1#include <iostream> 2 3class Cat { 4public: 5 Cat() { 6 std::cout << "Cat constructor called" << std::endl; 7 } 8 virtual ~Cat() { 9 std::cout << "Cat destructor called" << std::endl; 10 } 11 virtual void voice() { 12 std::cout << "Meow!" << std::endl; 13 } 14}; 15 16class Lion : public Cat { 17public: 18 Lion() { 19 std::cout << "Lion constructor called" << std::endl; 20 } 21 ~Lion() { 22 std::cout << "Lion destructor called" << std::endl; 23 } 24 void voice() override { 25 std::cout << "Roar!" << std::endl; 26 } 27};

The Cat destructor is marked as virtual to ensure that the derived class destructor is called first.

Destructors in Inheritance: Part 2

Let's see it in action:

C++
1int main() { 2 Lion lion; 3 4 // Output: 5 // Cat constructor called 6 // Lion constructor called 7 // Lion destructor called 8 // Cat destructor called 9 10 return 0; 11}

In this example:

  • When the lion object is destroyed, the Lion destructor is called first, followed by the Cat destructor.
  • This ensures that any resources allocated by derived classes are properly released before the base class destructor runs.
Lesson Summary and Practice Introduction

Today, we explored the concept of inheritance in C++. We learned about:

  • The importance and syntax of inheritance.
  • How to customize inherited methods through function overriding.
  • The role of constructors and destructors in inheritance.

Great job on getting through the lesson! Now, it's time to put your knowledge into practice. In the upcoming practice sessions, you will create your own base and derived classes, override methods, and use them accordingly. This hands-on experience will help solidify your understanding of inheritance in C++. Let's get started!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.