How it works...

Type erasure (or type erasing) is simply the act of removing, hiding, or reducing type information about an object, function, and so on. In the C language, type erasure is used all the time. Check out this example:

int array[10];
memset(array, 0, sizeof(array));

In the preceding example, we create an array of 10 elements, and then we use the memset() function to clear the array to all zeros. The memset() function in C looks something like this:

void *memset(void *ptr, int value, size_t num)
{
size_t i;
for (i = 0; i < num; i++) {
((char *)ptr)[i] = value;
}

return ptr;
}

As shown in the preceding code snippet, the first parameter the memset() function takes is void*. The array in our preceding example, however, is an array of integers. The memset() function doesn't actually care what type you provide, so long as you provide a pointer to the type and a size that represents the total size of the type in bytes. The memset() function then proceeds to type cast the provided pointer to a type that represents a byte (in C, this is usually char or unsigned char), and then sets the value of the type, byte by byte.

The use of void* in C is a form of type erasure. This type (pun intended) of erasure in C++ is typically discouraged as the only way to get the type information back is to use dynamic_cast(), which is slow (it requires a runtime type information lookup). Although there are many ways to perform type erasure in C++ without the need for a void *, let's focus on inheritance.

Inheritance is not generally described as type erasure in most literature, but it is likely the most widely used form of it. To better explore how this works, let's look at a common example. Suppose we are creating a game with multiple superheroes the user can choose from. Each superhero at some point has to attack the bad guy, but how the superhero attacks the bad guy varies from hero to hero.

For example, consider the following code snippet:

class spiderman
{
public:
bool attack(int x, int) const
{
return x == 0 ? true : false;
}
};

As shown in the preceding code snippet, our first hero doesn't care whether the bad guy is on the ground or in the air (that is, the hero will successfully hit the bad guy regardless of the bad guy's vertical distance), but will miss the bad guy if they are not in a specific horizontal position. Likewise, we might also have another hero as follows:

class captain_america
{
public:
bool attack(int, int y) const
{
return y == 0 ? true : false;
}
};

The second hero is the complete opposite of our first. This hero can successfully hit the bad guy anywhere on the ground but will miss if the bad guys is anywhere above the ground (the hero probably cannot reach them).

In the following example, both superheroes are fighting the bad guy at the same time:

    for (const auto &h : heroes) {
std::cout << h->attack(0, 42) << ' ';
}

Although we could call each superhero one at a time during the fight, it would be a lot more convenient if we could just loop through each hero in the fight and check to see which hero hits the bad guy versus which hero misses the bad guy.

In the preceding example, we have a hypothetical array of heroes that we loop through, checking to see which hero hits versus which hero misses. In this example, we don't care about the hero's type (that is, we don't care whether the hero is specifically our first or second hero), we simply care that each hero is actually a hero (and not an inanimate object) and that the hero is capable of attacking the bad guy. In other words, we need a way to erase each superhero's type so that we can put both heroes into a single array (which is not possible unless each hero is the same).

As you probably have already guessed, the most common way to accomplish this in C++ is to use inheritance (but as we will show later on in this chapter, it is not the only way). To start, we must first define a base class called hero, which each hero will inherit from, as follows:

class hero
{
public:
virtual ~hero() = default;
virtual bool attack(int, int) const = 0;
};

In our example, the only common function between each hero is that they both can attack the bad guy, the attack() function is the same for all heroes. As a result, we have created a pure virtual base class with a single pure virtual function called attack() that each hero must implement. It should also be noted that for a class to be pure virtual all member functions must be set to 0, and the class's destructor must be explicitly labeled as virtual.

Now that we have defined what a hero is, we can modify our heroes to inherit this pure virtual base class, as follows:

class spiderman : public hero
{
public:
bool attack(int x, int) const override
{
return x == 0 ? true : false;
}
};

class captain_america : public hero
{
public:
bool attack(int, int y) const override
{
return y == 0 ? true : false;
}
};

As shown, both heroes inherit from the pure virtual definition of a hero and override the attack() function as required. With this modification, we can now create our list of heroes as follows:

int main(void)
{
std::array<std::unique_ptr<hero>, 2> heros {
std::make_unique<spiderman>(),
std::make_unique<captain_america>()
};

for (const auto &h : heros) {
std::cout << h->attack(0, 42) << ' ';
}

return 0;
}

From the preceding code, we observe the following:

  • We create an array of hero pointers (using std::unique_ptr to store the lifetime of the hero, a topic that will be discussed in the next chapter).
  • This array is then initialized to contain two heroes (one of each).
  • Finally, we loop through each hero to see whether the hero successfully attacks the bad guy or misses.
  • When the hero::attack() function is called, the call is routed automatically to the correct spiderman::attack() and captain_america::attack() functions as needed through the use of inheritance. 

The array is erasing the type information of each hero in a type-safe manner to place each hero into a single container.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset