C++ Static Casting with Pointers (std::static_cast)

Learn how we can "downcast" our pointers to let us access functions in derived object types.
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 fantasy monster
Ryan McCombe
Ryan McCombe
Posted

In our lessons on inheritance and polymorphism, we talked about how an object of type Goblin also has the type of every parent class of Goblin. In previous examples, that included Character.

We could demonstrate this by treating a Goblin* (pointer to a Goblin) as a Character* (pointer to a Character)

class Character {
public:
  void TakeDamage() {};
};

class Goblin : public Character {
  void Enrage() {};
};

Goblin Harry;
Character* HarryPointer { &Harry };

We then also saw, through our Character*, we could call functions that exist on the character class:

Goblin Harry;
Character* HarryPointer { &Harry };
HarryPointer->TakeDamage();

Because TakeDamage is a function of the Character class, we are able to use it from our Character*.

But what if we wanted to call the Enrage function instead? After all, Harry is a Goblin, so we should be able to do that:

Goblin Harry;
Character* HarryPointer { &Harry };
HarryPointer->Enrage();

This, unfortunately, results in an error:

error: no member named 'Enrage' in 'Character'

Perhaps this makes sense. When we have a variable of type Goblin*, and we call a function on that object that is defined in the Character class, this is totally safe. By the rules of inheritance, all Goblins are also Characters

But, this is not true in the opposite direction. Not all Characters are guaranteed to be Goblins. Therefore, when we have a variable of type Character*, and we try to call a function that is defined in the Goblin class, we get a compilation error.

Downcasting in C++

This is another scenario where we need to rely on casting. Specifically, we have a pointer to a Character, and we want to convert this to be a pointer to a Goblin.

This scenario, where we are casting a pointer down the inheritance tree to a more specific subtype, is called downcasting.

The first thing to appreciate about this type of casting is that it might not work. Specifically, when we have a pointer of type Character* and we try to cast it to a pointer of type Goblin*, there are three possible situations:

  • Goblin does not inherit from Character. In this scenario, the cast will never work.
  • Goblin does inherit from Character, and we're certain our Character* is pointing at a Goblin. This is the case in our simple 3 line code example above. HarryPointer is definitely pointing at a Goblin so in this scenario, the cast will always work.
  • Goblin does inherit from Character, but we don't know for sure that our pointer is pointing at a Goblin. This is the most common case when dealing with polymorphism. In this scenario, the cast might work.

The first two scenarios can be handled by static_cast, which we covered in a previous lesson.

Static Casting C++ Pointers

We have seen before how we can explicitly convert to different data types using static_cast.

That works in this scenario too. Using static_cast, we can take our Character pointer and convert it to a Goblin pointer.

We can then Enrage using our Goblin pointer.

Goblin Harry;
Character* HarryPointer { &Harry };

static_cast<Goblin*>(HarryPointer)->Enrage();

This may seem contrived - why not just use a Goblin* to store a pointer to a Goblin?

This would work, but is rarely an option when dealing with polymorphism. After all, the point of polymorphism is that code such as our Combat and Act functions need to work with any type of Character.

Therefore, their parameter list need to be using Character pointers, not specific subtypes. We will, therefore, be dealing with Character* that we know (or think might be) pointing to more specific subtypes.

Test your Knowledge

Given the following code, how can we call the Bark function through the MyDogPointer variable?

class Animal {};

class Dog : public Animal {
public:
  void Bark() {};
}

Dog MyDog;
Animal* MyDogPointer { &MyDog };

Impossible Downcasts

One of the benefits of static_cast over C-style casting comes up in this downcasting use case.

Imagine we had the following code:

class Character {};
class Goblin {}; // No inheritance

Character Harry;
Character* HarryPointer { &Harry };

Goblin* GoblinPointer {
  static_cast<Goblin*>(HarryPointer)
};

Here, Goblin does not inherit from Character, so it is impossible that HarryPointer points to a Goblin. Because we're using static_cast, we get alerted to the fact our code does not make sense:

static_cast from 'Character *' to 'Goblin *', which are not related by inheritance, is not allowed

C-style casting does no such checking. It lets our code build, and the bug be introduced:

class Goblin {};
class Character {};

Character Harry;
Character* HarryPointer { &Harry };
Goblin* GoblinPointer {
  (Goblin*)HarryPointer
};

We then have a broken Goblin* variable in our code. If we go on to use it at run time, the results are unpredictable, but most likely a crash.

Static Casting vs Dynamic Casting

In the first example, static_cast works well, because we know ahead of time that HarryPointer, even though it is a Character pointer, will only always be pointing at a Goblin.

That's the only possible outcome of our simple 3 lines of code. But, this is not always going to be the case.

A limitation of static_cast is it only checks if Goblin is a subclass of Character. It does not check that this specific Character is a Goblin.

Lets change our earlier example slightly:

class Character {
public:
  void TakeDamage() {};
};

class Goblin : public Character {
  void Enrage() {};
};

Character Harry;
Character* HarryPointer { &Harry };

Goblin* GoblinPointer {
  static_cast<Goblin*>(HarryPointer)
};

In this scenario, even though Harry is no longer a Goblin, static casting his pointer to a Goblin* seems to have worked.

If we then go on to use that pointer, we're likely to see our application crash:

GoblinPointer->Enrage();

We saw that static_cast will check at compile time if our inheritance tree makes such a cast possible in theory.

In this case, it is possible - Goblin inherits from Character so a Character* might be pointing at a Goblin.

But, static_cast doesn't do any run time checks to make sure. Not checking at run time is good for performance - if we know our cast will work, checking that it did work wastes some resources. So, if we know it will work, we should use static_cast and enjoy the performance benefits.

However, often we can't be sure. Often, the specific subtypes our functions are dealing with will depend on runtime scenarios, like user input.

So, we'll have scenarios where a Character* could be a Goblin*, but might not be. This is very common in polymorphism.

For those scenarios, we have dynamic casting, which we'll cover in the next lesson.

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

Polymorphism
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++ Dynamic Casting (std::dynamic_cast)

An introduction to dynamic casting of pointers in C++ with examples using std::dynamic_cast
3D art showing a woman in a dark fantasy environment
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved