Replacing Inherited Functions

Learn how our classes can replace functions that they are inheriting from their parents.
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

A screenshot showing a space ship
Ryan McCombe
Ryan McCombe
Posted

This Article has been Archived

A new version of this article has been published:

We've already seen how our classes can inherit functions from their ancestors. A common requirement is that child classes be able to override the functions they're inheriting.

Lets imagine we have the following classes:

class Character {
public:
  void TakeDamage(int Damage) {
    Health -= Damage;
  };
protected:
  int Health { 100 };
};

class Hero : public Character {
private:
  float Armour { 0.2f };
};

Our goal is to let our Hero objects take reduced damage, based on their Armour value

The problem is the TakeDamage function is something it is inheriting from the Character, and we don't want all Characters to have armour, just specifically heroes.

Implementing Override Functions

To implement this, we need to override the parent implementation of TakeDamage from within our Hero class.

To do this, we provide a function on our Hero class with the same prototype and the same access level (eg, public) as the function we want to override.

class Character {
public:
  void TakeDamage(int Damage) {
    Health -= Damage;
  };
private:
  int Health { 100 };
};

class Hero : public Character {
public:
  void TakeDamage(int Damage) {
    Health -= (Damage * (1-Armour));
  };
private:
  float Armour { 0.2f };
};

Now, a different version of the function will be called, depending on the type of the object it is being called with:

Character Monster;
// Monster will lose 100 health
Monster.TakeDamage(100);

Hero Player;
// Player will lose 80 health
Player.TakeDamage(100);

Test your Knowledge

After running the following code, what is the value of LegCount?

class Animal {
public:
  int CountLegs() { return 4; }
};

class Spider : public Animal {
public:
  int CountLegs() { return 8; }
};

Animal Daisy;
int LegCount { Daisy.CountLegs() };

After running the following code, what is the value of LegCount?

class Animal {
public:
  int CountLegs() { return 4; }
};

class Spider : public Animal {
public:
  int CountLegs() { return 8; }
};

Spider Spooky;
int LegCount { Spooky.CountLegs() };

After running the following code, what is the value of LegCount?

class Animal {
public:
  int CountLegs() { return 4; }
};

class Spider : public Animal {
public:
  int CountLegs() { return 8; }
};

class Tarantula : public Spider {};

Tarantula Hairy;
int LegCount { Hairy.CountLegs() };

This is a powerful tool because, in the future, we will be writing code that calls functions on objects without necessarily knowing what subtype of object it is. It might be a plain old Character; it might be a Monster, or Hero or Goblin, or any other derivative of Character.

This means that, in the future, we will be able to expand our game incredibly easily. We will be able to add new types of characters to our game without having to update any of our other code - such as the Combat or Logging systems we've used in previous challenges.

This is a critical concept of object oriented programming, called polymorphism. In the next section of the course, we will be extending the ideas here to unlock the full potential of polymorphism.

For now though, lets explore an important design option when overriding functions.

Extending Base Functions and the :: operator

Commonly, our use case for replacing a function in this way is not necessarily to replace the base implementation entirely. More commonly, we just want to extend the base implementation.

For example, in a real game, when a Character takes damage, it's generally going to require more than updating a single Health value. We could be dealing with animation, sound, blood effects, AI and more.

class Character {
public:
  void TakeDamage(int Damage) {
    Health -= Damage;
    PlayAnimation();
    PlaySound();
    UpdateAI();
    RenderEffects();
    /// etc
  };
protected:
  int Health { 100 };
};

We probably don't want to replace (or duplicate) all of that in our override - we just want to reduce the Damage slightly. To achieve this, we can call the parent's implementation of the function with a smaller number.

class Hero : public Character {
public:
  void TakeDamage(int Damage) {
    int ReducedDamage { Damage * (1-Armour) };
    // Pass ReducedDamage to the parent TakeDamage function
  };
private:
  float Armour { 0.2f };
};

Given we've overridden the TakeDamage function, we can't just access it using TakeDamage from our Hero class. Our hero class now has its own version of TakeDamage

As we've seen with how scopes work in the past, the compiler will use the "most local" version of a variable or function. Given we're writing code in the Hero class, the most local function is the one that is also in the Hero class.

Fortunately, we can instruct the compiler what scope to use. We do that with the scope resolution operator ::

By prefixing our function call with Character:: we can specify we want to access the function that is defined in the Character class, rather than the one in our current scope.

class Hero : public Character {
public:
  void TakeDamage(int Damage) {
    int ReducedDamage { Damage * (1-Armour) };
    Character::TakeDamage(ReducedDamage);
  };
private:
  float Armour { 0.2f };
};

Now, when we or another developer adds features or fixes a bug in the Character's TakeDamage function, our Hero benefits from that too. This makes our code easier to maintain and less prone to bugs.

Unreal Engine and Super

Many other object oriented language allow developers to use the Super keyword to refer to the parent class. This is not a feature in C++

However, when working with a C++ project generated by Unreal Engine, our code will run through a type of preprocessor called the Unreal Header Tool.

This tool adds some additional features to our classes, and the Super keyword is included in that:

void TakeDamage(int Damage) {
  int ReducedDamage { Damage * (1-Armour) };
  Super::TakeDamage(DamageToTake);
};

Using it is optional - we could still use Character:: if we preferred.

Test your Knowledge

After running the following code, what is the value of LegCount?

class Animal {
public:
  int CountLegs() { return 4; }
};

class Cow : public Animal {
  int CountLegs() {
    cout << "Moo";
    return Animal::CountLegs();
  }
};

Cow Kevin;
int LegCount { Kevin.CountLegs() };

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
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

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:

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

C++ References - Why We Need Them

A deeper look at what is happening when we pass arguments to functions, and an introduction to the problem that references are designed to solve.
3D art showing a female blacksmith character
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved