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.
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:
Render()
functionHaving 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.
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.
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.
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.
Now, our Goblin
class has the Render
ability, without needing to write any additional code. It just inherits it from the parent class.
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.
What is 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(){}
};
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();
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
?
Let's add a new type of object to our game - we need dragons, so let's define a type, using the same approach:
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
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();
}
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'
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:
Goblin
and Actor
classes.final
keyword to prevent further inheritance from a class.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.
In this lesson, we delve into C++ inheritance, guiding you through creating and managing class hierarchies to streamline your code
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way