C++ Dynamic Casting (std::dynamic_cast)

An introduction to dynamic casting of pointers in C++ with examples using std::dynamic_cast
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 woman in a dark fantasy environment
Ryan McCombe
Ryan McCombe
Posted

Lets imagine our base Character class look like this:

class Character {
public:
  virtual void Act(Character* Enemy) {
    Enemy->TakeDamage(50);
  }
  
  virtual void TakeDamage(int Damage) {
    Health -= Damage;
  }
protected:
  int Health { 200 };
}

Vampires are about to enter the battle, and they can shrug off any damage! Lets override the TakeDamage function to make that happen:

class Vampire : public Character {
public:
  void TakeDamage(int Damage) override {
    cout << "Muahahaha";
  }
}

They've been defeating everyone. They can be killed by wooden stakes, but nobody has uncovered this fatal weakness:

class Vampire : public Character {
public:
  void TakeDamage(int Damage) override {
    cout << "Muahahaha";
  }
  
  void Stake() {
    Health = 0;
  }
}

Finally, some Vampire Hunters show up, with the secret knowledge and a solid plan:

class VampireHunter : public Character {
public:
  void Act(Character* Enemy) override {
    if (EnemyIsVampire) {
      Enemy->Stake();
    } else {
      Enemy->TakeDamage(30);
    }
  }
}

Lets see how we can get that working!

The Use Case for Dynamic Casting

In the previous lesson we saw that, if we knew the Enemy was pointing to a Vampire, we could just use static_cast.

void Act(Character* Enemy) override {
  Vampire* VampireEnemy {
    static_cast<Vampire*>(Enemy)
  };
  
  VampireEnemy->Stake();
}

This will work as long as the Enemy really is a Vampire, but we can't know that. Our VampireHunters will be able to fight other types of Character too, so we need a better solution.

When we need to try a cast, but we're not sure it will work, we should use dynamic_cast

Dynamic casts are done at run-time, so they have a performance cost. What we get for that cost is information on whether or not the cast was successful.

We use dynamic_cast in the same way we would use static_cast:

dynamic_cast<Vampire*>(Enemy);

However, now we can now check if the resulting pointer is valid before trying to use it. We can do that with a simple if check on the result of calling dynamic_cast.

1void Act(Character* Enemy) override {
2  if (dynamic_cast<Vampire*>(Enemy)) {
3    // enemy is a vampire
4    // TODO: call the stake function
5  } else {
6    // enemy is not a vampire
7    Enemy->TakeDamage(30);
8  }
9}
10

Within the if statement, we want to call the Stake function.

Stake is specifically a function that exists on Vampire. Enemy is a Character pointer, so we can't just do Enemy->Stake().

Instead, we could do the cast twice - once to check if it's a vampire, and again on line 4 to access the Stake function.

1void Act(Character* Enemy) override {
2  if (dynamic_cast<Vampire*>(Enemy)) {
3    // enemy is a vampire
4    dynamic_cast<Vampire*>(Enemy)->Stake();
5  } else {
6    // enemy is not a vampire
7    Enemy->TakeDamage(30);
8  }
9}
10

But it would be cleaner to just do the cast once, and save it in a variable that we can reuse:

void Act(Character* Enemy) override {
  Vampire* VampireEnemy {
    dynamic_cast<Vampire*>(Enemy)
  };
  
  if (VampireEnemy) {
    VampireEnemy->Stake();
  } else {
    Enemy->TakeDamage(30);
  }
}

This technique only works with dynamic_cast. Static casting does not check if the cast is valid at run time. The pointer generated from a static_cast will always point to something.

When creating a variable using dynamic_cast, we always want to make sure it succeeds before trying to use it.

Test your Knowledge

Consider the following code:

void MakeNoise(Animal* SomeAnimal) {
  static_cast<Dog*>(SomeAnimal)->Bark();
}

If we are not certain that the SomeAnimal parameter is pointing to a Dog, what should we change in the above code?

When Dynamic Casting Fails

Something interesting might have caught our attention about the previous code example:

Vampire* VampireEnemy {
  dynamic_cast<Vampire*>(Enemy)
};
  
if (VampireEnemy) {
  VampireEnemy->Stake();
} else {
  Enemy->TakeDamage(30);
}

Given how we're using the result of dynamic_cast within the if statement, it seems there are two possible outcomes:

  • dynamic_cast returns a pointer to a Vampire, or
  • dynamic_cast returns something that is false when used as a boolean

This is indeed true. Dynamic casting always returns a pointer but, if the cast failed, what we get is something called a null pointer. A null pointer is falsey, ie, if we treat it as a boolean like in our if statement above, it will be treated as the boolean value false.

What if we didn't check using the if statement? We may have ended up trying to dereferencing a null pointer using the * or -> operator. This will result in an error at run time - most likely a crash.

Therefore, when we create a pointer using dynamic_cast, we should always be considering the possibility that the cast failed before using the pointer it returned.

C++ Null Pointers (nullptr)

Any pointer can be a null pointer - they are not just the result of casting. Therefore, when using pointers, it is generally recommended to ensure the pointer we receive is valid before trying to dereference it.

In the below example, we are just returning from the function immediately. This means our specific function will not cause a crash.

void Combat(Character* Player, Character* Enemy) {
  if (!Player || !Enemy) {
    cout << "Combat function received a null pointer!";
    return;
  };
  Player->Act(Enemy);
  Enemy->Act(Player);
}

"But this pointer should never be null!"

In many situations, we are writing code that is working with a pointer that we expect to sometimes be null. Doing an if check and implementing some alternative behaviour is a reasonable way to deal with such scenarios.

However, there are other times where we are operating on a pointer that we would never expect to be null. If it is null, that would mean something had fundamentally gone wrong in our program.

In these cases, we should still check if the pointer is null, because things do go wrong all the time. However, the strategy of having our code "silently fail" in such scenarios is not ideal.

If something is causing our program to get into a state we never expected to be possible, we have a bug. We'd like to be alerted to that bug so we can fix it. Having our functions try to work around the weirdness might be hiding the issue from us.

We will see some specific strategies for dealing with errors in the intermediate course. For now, handling these scenarios with silent failures, using cout to log an error message, or just letting the program crash are resonable approaches.

Test your Knowledge

Consider the following function:

void MakeNoise(Animal* Dog) {
  Dog* SomeDog { dynamic_cast<Dog*>(SomeAnimal) };
}

If we are not certain that SomeAnimal is pointing to a Dog, what is a safe way of calling the Bark function through the SomeDog variable?

Null pointers are not a bad thing. Often, our pointers should be null.

For example, our character class might have a Target* field, which is a pointer to the character that the player has currently selected.

If the player has not selected anything, having that field be a null pointer would be sensible.

Creating Null Pointers in C++

We often want pointers to start off as null pointers. If we do not explicitly give a pointer an initial value, it will point to some garbage location.

int* SomePointer;
cout << SomePointer;

This code logs out an arbitrary memory address, such as 0x7ffc0a866a60.

We use the nullptr token to set a value to the null pointer:

int* SomePointer { nullptr };
cout << SomePointer;

This code now logs out the memory address of simply 0, which we can think of as being "a pointer to nothing"

Why do variables not have sensible default values?

This would seem like very strange behaviour. If we don't specify an initial value, why doesn't the compiler just choose an appropriate default, like nullptr?

The design of C++ follows a "zero overhead" philosophy, and initialising a variable has a small performance overhead.

Assigning a value to a variable requires a CPU instruction. Therefore, it is not done by default. If we want our variables to have some default value, we need to take care of that ourselves.

However, most compilers and code editors will warn us if our code tries to use a variable that has not been initialised, so we are afforded some help from our tools.

Having a pointer pointing to an arbitrary, unknown memory address can be a problem. Even if we run an if check on one of these pointers before trying to use it, that check will return true.

After all, an uninitialised pointer is not a null pointer. It actually is pointing to something, just not something we intended.

As a result, the following code will compile, but will result in unpredictable results when run - most likely a crash:

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

int main() {
  Character* Pointer;

  // This will be true - we are pointing to something
  if (Pointer) {
    // but it's almost certainly not pointing to a character
    Pointer->Dance();  // crash!
  }
}

Because of this, if we do not have an initial value we want to use for a pointer, we should set it to be a null pointer explicitly, using the nullptr syntax:

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

int main() {
  Character* Pointer { nullptr };

  // This will be false now, and our code will be safe
  if (Pointer) {
    Pointer->Dance();
  }
}

Test your Knowledge

Which of the following statements is creating a null pointer?

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++ Preprocessor - #ifdef

An introduction to the C++ preprocessor, and how we can automatically prevent parts of our code from being included when we compile
3D art showing a character in a bar
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved