We've already seen how our classes can inherit functions and variables from their ancestors. A common requirement is that child classes be able to change the things they're inheriting.
Lets imagine we have the following classes:
class Character {
public:
int GetHealth() {
return Health;
};
int Health { 100 };
};
class Monster : public Character {
public:
int Health { 200 };
};
Our goal is that both our Character
and more specific Monster
objects to have a Health
property, but for their value to be different.
Lets create a Monster
and see if our code works:
Monster SomeGoblin;
cout << SomeGoblin.Health << endl;
cout << SomeGoblin.GetHealth();
Our output is:
200
100
This is not quite what we want. SomeGoblin.Health
is returning the Health
value from our Monster
class, but GetHealth()
is using the value from our Character
class.
This behaviour is quite similar to what we previously saw in our introductory lesson on scopes. There, we saw when we refer to a variable, the most local version of that variable is used.
Just like we saw in that lesson, the problem is that Health
in the Character class and Health
in the Monster class are different variables. They just happen to have the same name. We're "shadowing" or "masking" the variable, not replacing it.
Since SomeGoblin
is a Monster
, when we create an expression like SomeGoblin.Health
, we look for the Health
variable within the Monster
class. That variable exists, so it is used.
However, when we have the expression SomeGoblin.GetHealth()
, there is no GetHealth
function in our Monster
class, so the compiler searches up the inheritance tree.
It finds GetHealth
exists on the parent scope, so it uses that function.
However, now that we are in the scope of the Character
class, we no longer have access to things that are in the child scope. The Health
variable of the Monster
class is out of reach, so the GetHealth
function returns the value stored in the Character
class.
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() };
After running the following code, what is the value of LegCount
?
class Animal {
public:
int CountLegs() { return LegCount; }
int LegCount { 4 };
};
class Spider : public Animal {
public:
int LegCount { 8 };
};
Spider Spooky;
int LegCount { Spooky.CountLegs() };
After running the following code, what is the value of LegCount
?
class Animal {
public:
int CountLegs() { return LegCount; }
};
class Spider : public Animal {
public:
int LegCount { 8 };
};
Spider Spooky;
int LegCount { Spooky.CountLegs() };
So, how can we solve this simple problem of letting our Monster
objects have a different Health
value? One option is duplicating the GetHealth
function into the class:
class Character {
public:
int GetHealth() {
return Health;
};
int Health { 100 };
};
class Monster : public Character {
public:
int GetHealth() {
return Health;
};
int Health { 200 };
};
But this loses all the advantages of inheritance that we talked about. A better option would be to update the inherited value from the default constructor:
class Character {
public:
int GetHealth() {
return Health;
};
protected:
int Health { 100 };
};
class Monster : public Character {
public:
Monster() {
Health = 200;
};
};
We've additionally moved the Health
variable into the protected section. This is not necessary, but is a good practice, as described in our lessons on encapsulation.
Now, our inherited class works as we expect:
Monster SomeGoblin;
cout << SomeGoblin.GetHealth();
Our output is:
200
::
operatorAnother situation we're likely to run in to is one where we need to replace an inherited function:
class Character {
public:
void TakeDamage() {
cout << "I have taken damage" << endl;
};
};
class Monster : public Character {
public:
void TakeDamage() {
cout << "You will pay for that!" << endl;
};
};
This behaves as we might expect:
Monster SomeGoblin;
SomeGoblin.TakeDamage();
You will pay for that!
This is straightforward when the function we want to replace is very simple.
However, that will not always be the case. The TakeDamage
function might have lots of moving parts and, from our inherited function, we still want to do all of those things. We just want to do something extra, too.
Rather than duplicating the entire function, we can instead call the base class function from our inherited class function. However, given the functions have the same name, it's not too clear how to do that.
Doing it directly won't work - we'll just create an infinite loop:
class Monster : public Character {
public:
void TakeDamage() {
TakeDamage();
cout << "You will pay for that!" << endl;
};
};
Fortunately, we have the Scope Resolution operator, ::
.
Where there are multiple functions or variables with the same name available within a scope, we can use the ::
operator to specify which one we want to use. This lets us bypass the default "use the most local scope" behaviour when we try to access a variable or function.
class Character {
public:
void TakeDamage() {
cout << "I have taken damage" << endl;
// Imagine lots of additional code here
};
};
class Monster : public Character {
public:
void TakeDamage() {
Character::TakeDamage();
cout << "You will pay for that!" << endl;
};
};
Now, we have unlocked the ability to extend inherited functions, rather than just replacing them entirely.
Monster SomeGoblin;
SomeGoblin.TakeDamage();
I have taken damage
You will pay for that!
After running the following code, what is the value of LegCount
?
class Animal {
public:
int LegCount { 4 };
};
class Spider : public Animal {
public:
int CountLegs() {
return Animal::LegCount;
}
int LegCount { 8 };
};
Spider Spooky;
int LegCount { Spooky.CountLegs() };
Super
doesn't exist (*)Many other object oriented language allow developers to use the Super
keyword to refer to the parent class.
This has the benefit where, if our class is updated to inherit from a different base class (sometimes referred to as "reparanting"), the Super
keyword will automatically reflect that.
This is not a feature in C++, because C++ supports multiple inheritance. We'll cover this in more detail in the intermediate course, but the summary is that it's possible for a class to have multiple base classes. So, it's not clear which class Super
would refer to.
However, in some environments, multiple inheritance is restricted, and Super
is available.
This includes C++ code written for Unreal Engine, which is passed through a custom preprocessor (the Unreal Header Tool) before it is sent to the compiler.
One thing that preprocessor does is allow us to use Super
:
// This only works if our code is handled by Unreal
class MyClass : public SomeUnrealClass {
void TakeDamage() {
Super::TakeDamage();
cout << "You will pay for that!";
};
}
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way