So I'm working on a text-based RPG, and I've run into an issue. I am currently working on equipping weapons from the character's inventory. I am trying to make it so that my program can tell if the item they want to equip is of class Weapon or not. Here is the clip of relevant code:
Item tempChosenWeapon = myInventory.chooseItem();
cout << tempChosenWeapon.getName() << endl;
Item *chosenWeapon = &tempChosenWeapon;
cout << chosenWeapon->getName() << endl;//THE CODE WORKS UP TO HERE
Weapon *maybeWeapon = dynamic_cast<Weapon*>(chosenWeapon);
cout << maybeWeapon->getName() << endl;
Now, Weapon is a child class of Item, which is why I am using dynamic cast -- in an attempt to change chosenWeapon, which is of type Item, to type Weapon in order to compare the two classes. (I am using these cout<<s in or to test whether or not calling a function from these objects works).
My program compiles, and everything runs fine until we come to maybeWeapon->getName(), in which the program crashes. I've researched quite a bit, but I just don't understand what I am doing wrong. Any answer or alternative suggestion is much appreciated! Thanks!
The problem is that you try to make a dynamic cast to a Weapon but in reality the object pointed to is a true copy constructed Item and not a subclass. This is results in a nullptr and UB when you dereference it !
Let's suppose that you have only Weapon objects in your inventory. The first instruction in your snippet is the root of your evil:
Item tempChosenWeapon = myInventory.chooseItem();
This is statement is a copy construction of an Item object. If the source object was a Weapon, it will be sliced.
Later you take a pointer to this object:
Item *chosenWeapon = &tempChosenWeapon;
But this Item* doesn't point to a Weapon object as you think. It points to a real crude Item object ! So when you do the dynamic cast here:
Weapon *maybeWeapon = dynamic_cast<Weapon*>(chosenWeapon);
the code will find out that choosenWeapon is not a Weapon*, and the result of dynamic_cast will be a nullptr. Until now it's not necessarily a catastrophe. But when you then derefence this pointer you get UB:
maybeWeapon->getName() // OUCH !!!!!!
Checking if the dynamic_cast was successful (i.e. result not nullptr) is a protection against the crash, but will not solve your root problem.
It is even possible that the problem is even deeper than expected: what type does the myInventory.chooseItem() return in reality ? Is it a plain Item ? Then you might have the slicing problem already in the inventory !
If you want to use polymorphism:
Item: you'd need to invoke a polymorphic clone() function and ensure that the target of this cloning has a compatible type. To start with a solution, it's something like this:
Item* chosenWeapon = myInventory.chooseItem(); // refactor choosItem() to return a pointer.
cout << chosenWeapon->getName() << endl;
Weapon *maybeWeapon = dynamic_cast<Weapon*>(chosenWeapon);
if (maybeWeapon)
cout << maybeWeapon->getName() << endl;
else cout << "Oops the chosen item was not a weapon" <<endl;
If this still not work, then your inventory container would be flawed. In this case, look at this question before opening a separate question with the code of your container
dynamic_cast will return nullptr if the pointer cast cannot be performed (for reference casts it will throw an exception), so your code should read something like:
Weapon *maybeWeapon = dynamic_cast<Weapon*>(chosenWeapon);
if ( maybeWeapon ) {
cout << maybeWeapon->getName() << endl;
else {
// it's not a weapon
}
If you don't perform that test, and try to dereference the pointer containing nullptr, you are off in Undefined Behaviour Land.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With