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.
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);
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.
::
operatorCommonly, 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.
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.
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() };
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way