Inheritance

In this lesson, we delve into C++ inheritance, guiding you through creating and managing class hierarchies to streamline your code
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
3D art showing a fantasy vampire character
Ryan McCombe
Ryan McCombe
Edited

In the previous chapter, we saw how we could create user-defined types, helping us design our code in a way that makes sense for the specific project we’re working on.

For example, were we making a game where players can fight goblin enemies, we could have a Goblin class, containing all our goblin-specific code:

class Goblin {
public:
  void Render(){}
  void Move(){}
  void Attack(){}
  void DropLoot(){}
  void Enrage(){}
};

When we start implementing more complex systems using classes, we quickly encounter a problem.

Why we need Inheritance

Our game will probably need some simple objects we can scatter around the environment, like rocks.

Rocks aren’t as complicated as goblins - they just need to be rendered to the screen:

class Rock {
public:
  void Render(){}
};

We have a bit of a problem here - both of our classes have a Render() method. In fact, given we’re making a game, a lot of our types are going to need this method.

With what we’ve learned so far, there are no good ways to deal with this. Some options might be:

Option 1: Let both classes have a Render() function

Having the same code in multiple places is a pain - it makes our projects very complicated. Given we’re making a game, a lot of our types are going to need this function, so the problem gets worse and worse as we add more types.

Option 2: Repurpose the Goblin class so both Rocks and Goblins can share the same code

This reduces the need to duplicate code, but it creates extremely complicated classes. It also degrades performance - class functions like Attack() need class variables like int Damage to support those actions. Having every rock in our game carry those variables, which they never need, is a waste of resources.

To solve this problem, what we need is the ability to define multiple layers of abstraction, and that is what inheritance gives us.

Designing with Inheritance

Inheritance allows us to organize our classes into hierarchies. This allows a class to inherit the functions and variables of its parent.

For example, let's imagine a class that simply grants the object the ability to exist in our world, and render itself to the screen. We'll call this the Actor class.

A UML diagram showing the Actor class

Next, let's create a class for our Goblins. Goblins also need the ability to be rendered, but with inheritance, we no longer need to recreate that functionality. Actor already has that ability.

Goblins are just a more specific type of Actor, so we can have our Goblin class inherit all the abilities of the Actor class.

A UML diagram showing the Actor and Goblin classes

Now, our Goblin class has the Render ability, without needing to write any additional code. It just inherits it from the parent class.

Parents, Children, Subclasses, and Base Classes

Hierarchical structures like these are very common in programming. There are many different terms used to describe the position of something within the hierarchy.

Phrases associated with family relationships are often used, such as parent, child, ancestor, descendant, and more. For example:

  • Actor is a parent or an ancestor of Goblin
  • Goblin is a child or a descendant of Actor

Other popular terms include sub-class, base class, and derived class. For example:

  • Goblin is a sub-class of Actor
  • Goblin derives from Actor
  • Actor is the base class of Goblin

This tree structure provides our classes with a powerful ability. We get to have our dedicated, specific classes, but they don't need to duplicate the more generic, shared functions.

They can instead just inherit those functions and variables from their ancestors.

Test your Knowledge

Inheritance

What is inheritance?

Implementing Inheritance

Let's create our Actor class, and our Goblin class that inherits from Actor:. To do this, we add some additional syntax to our class Goblin definition:

class Actor {
public:
  void Render(){};
};

class Goblin : public Actor {
public:  
  void Move(){}
  void Attack(){}
  void DropLoot(){}
  void Enrage(){}
};

Public and Private Inheritance

Just like functions and variables, inheritance itself can be public or private. By default, class inheritance is private. In our previous example, we set it to public

The level of inheritance effectively sets the maximum visibility of any inherited functions.

Setting the inheritance to public will not overrule access restrictions within the base class. If something was private in Actor, it will still be private in Monster.

In practice, public inheritance is by far the most useful and most commonly used. If you ever notice issues where the compiler is refusing to let you call a public inherited function, ensure the inheritance itself is also public.

Now, when we create an object from the Goblin class, that object will be both a Goblin and an Actor, able to use the functions of both:

Goblin Bonker;

// Available because Bonker is a Goblin
Bonker.Attack();

// Available because Bonker is an Actor
Bonker.Render();

With this class hierarchy, all Goblin objects are inherently also Actor objects. The opposite is not necessarily true - an Actor is not necessarily also a Goblin.

It could just be a plain Actor, or it could be some other subclass of actor.

Actor Rock;

// Available because Rock is an Actor
Rock.Render();

// But we can't do this, because Rock is not a Goblin
Rock.Attack();
Test your Knowledge

Inheritance Relationships

Consider the following code:

class Item {};

class Weapon : public Item {};

int main(){
  Weapon IronSword;
}

What is IronSword?

Consider the following code:

class Item {};

class Weapon : public Item {};

int main(){
  Item WoodenBarrel;
}

What is WoodenBarrel?

Multiple Layers of Inheritance

Let's add a new type of object to our game - we need dragons, so let's define a type, using the same approach:

A UML diagram showing the Actor, Goblin and Dragon classes

Something should seem off here. The Dragon and Goblin classes both have the Move, Attack, and DropLoot functions. That's too much duplicate code.

Thankfully, with our new knowledge of inheritance, we might realize a way we can improve this design.

We can move that shared code into a new class. We can have both Goblin and Dragon inherit from it. Let's call this new class Character

A UML diagram showing the Actor, Character, Goblin, and Dragon classes

Now, we have multiple layers of inheritance. With this setup, any Goblin or Dragon we create will be a member of 3 classes, inheriting the abilities of all of them:

class Actor {
public:
  void Render(){}
};

class Character : public Actor {
public:
  void Move(){}
  void Attack(){}
  void DropLoot(){}
};

class Goblin : public Character {
public:
  void Enrage(){}
};

class Dragon : public Character {
public:
  void Fly(){}
};

int main(){
  Dragon Dave;

  // Dave is an Actor
  Dave.Render();

  // And a Character
  Dave.Attack();

  // And a Dragon
  Dave.Fly();
}

Preventing Inheritance with final

Sometimes, we create a class that is not designed to ever have a child class. To clarify that intent, and have the compiler enforce it, we can add the final specifier to the class heading:

class Demon : final {};

// Combining final with a base class
class Vampire final : public Character {};

Should anyone try to inherit from a final class, the compiler will block them:

class Warrior : public Vampire {}
Cannot inherit from 'Vampire' as it has been declared as 'final'

Summary

In this lesson, we delved into the concept of inheritance in C++. We explored how inheritance allows us to create hierarchies of classes, enabling child classes to inherit properties and behaviors from their parent classes.

This not only helps in organizing code into a more understandable structure but also aids in reducing redundancy.  We:

  • Learned the basics of inheritance and how it helps in organizing classes into hierarchies.
  • Explored public and private inheritance, understanding how access levels affect the inherited members.
  • Demonstrated the implementation of inheritance in C++ with practical examples, such as Goblin and Actor classes.
  • Discussed the use of the final keyword to prevent further inheritance from a class.
  • Examined multiple layers of inheritance through examples, showcasing how classes can inherit from other derived classes.

Next Lesson: Protected Class Members

Our next lesson will focus on protected class members. This concept helps us control access to class members in an inheritance hierarchy.

Protected members provide a level of accessibility that falls between public and private, which we’ll cover in detail.

Was this lesson useful?

Edit History

  • Combined and rewrote "Why we need Inheritance" and "Implementing Inheritance" lessons

  • First Published

Ryan McCombe
Ryan McCombe
Edited
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Inheritance
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access

This course includes:

  • 56 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

Protected Class Members

Learn about protected class members in C++, including how and when to use them in your code, especially in the context of inheritance and class design
3D3D art showing a dragon character
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved