Much of the content of this series relates to a programming concept called first-class functions.
A programming language supports first-class functions if its functions can be treated the same way as other types of data, like integers and booleans. For example:
C++ supports first-class functions in several different ways. In this chapter, we will cover the three main options we have: function pointers, function objects, and lambdas
But first, let's see a scenario where first-class functions are a useful feature
Let’s imagine we have a Party
class, which is what enables our characters to group up and take on bigger challenges.
class Party {
public:
Character* PlayerOne;
Character* PlayerTwo;
Character* PlayerThree;
Character* PlayerFour;
};
Elsewhere in our code, we’ll want to find out more information about this party. For example, we might want to check that everyone is alive:
bool isEveryoneAlive {
MyParty.PlayerOne->isAlive() &&
MyParty.PlayerTwo->isAlive() &&
MyParty.PlayerThree->isAlive() &&
MyParty.PlayerFour->isAlive()
};
Aside from requiring quite a lot of code, this implementation also makes a big assumption - the party will always have exactly 4 members. In reality, if a party has less, this code may throw an exception due to one of the characters being a nullptr
.
Worse, if we later expand our Party
class to allow additional characters, code like this may still compile, but return the incorrect result because it’s not checking if the new PlayerFive
is alive.
An immediate solution probably comes to mind - we can just add a function to our class to handle this:
class Party {
public:
bool isEveryoneAlive {
return (
MyParty.PlayerOne->isAlive() &&
MyParty.PlayerTwo->isAlive() &&
MyParty.PlayerThree->isAlive() &&
MyParty.PlayerFour->isAlive()
);
};
Character* PlayerOne;
Character* PlayerTwo;
Character* PlayerThree;
Character* PlayerFour;
};
Now, our code can just call MyParty.isEveryoneAlive()
That is better, but we’ve only really solved one specific use case. There are many possible questions people will want to ask of our party. For example:
MyParty.isEveryoneOnline();
MyParty.isEveryoneAtLeastLevel(50);
MyParty.isAnyoneAHealer();
We can’t predict all the future possibilities, and we wouldn’t want to need to write code for all of those anyway. Thankfully, this is one of the problems first-class functions were designed to solve.
To solve this problem with first-class functions, let's create a function that implements the actual check we want to do on each member of the party:
bool isAlive(Character* Character) {
return Character->isAlive();
}
To run this check on everyone in the party, we’d like some way to pass this function to a function on the Party
class. A method on our party class that checks if every character meets some condition could be called every
. So, our function call might look something like this:
bool isEveryoneAlive { MyParty.every(isAlive) };
The beauty of this design is that we can now ask anything of our Party
, without the class needing to be expanded or modified:
bool canStartQuest {
MyParty.every(isAlive) &&
MyParty.every(isOnline) &&
MyParty.every(isAtLeastLevel50) &&
MyParty.every(isInTheShire)
}
bool isAlive(Character* PartyMember) {
return PartyMember->isAlive();
}
bool isOnline(Character* PartyMember) {
return PartyMember->isOnline();
}
bool isAtLeastLevel50(Character* PartyMember) {
return PartyMember->GetLevel() >= 50;
}
bool isInTheShire(Character* PartyMember) {
return PartyMember->GetLocation() == LOCATIONS::SHIRE;
}
Over the coming lessons, we will see three ways code like this can be implemented in C++
In the next lesson, we will introduce the first of these: function pointers.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.