Jump to content
  • Advertisement
Sign in to follow this  
Eskapade

dynamic_cast confusion

This topic is 3490 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello fellow gamedevs, thanks for your time! Lately I was trying to implement an inventory as found in RPG games in C++, just to practice programming a bit. I took a look at the game "Phantasy Star Online" where one can find items with very varying attributes. These items are basically split into the categories weapon, armor, armor slot item, shield, Mag (some Tamagotchi-ish companion), consumables (potions etc). As I know that a player in this game can carry a maximum of 30 items, I decided to use an std::vector, reserve 30 elements and derive the different item categories from a base class "Item". I thought all items had a name, description and a rarity attribute, which turned out to be not true. Still I implemented it with these attributes at that time, so I could create an std::vector<Item> which would be able to store different item categories. I gave this class a virtual destructor so it would have a vtable in order to allow me dynamic_casting to the derived classes "Weapon", "Armor", etc. This worked really well. Everything was derived from "class Item" directly and I could add items in code like this:
Inventory inv; // uses an std::vector<Item> internally
Weapon weapon("Sange", "AUW 1963 model. Made by Jou'un.", Weapon::KATANA, 10); // set name, description, weapon type, rarity
weapon.SetATP(500, 550); // set attack power range
Consumable consumable = Consumable("Monomate", "Health recovery fluid. Restores HP. Restoration is based upon difficulty level");

// Add the items to inventory
inv.Add(weapon);
inv.Add(consumable);



When I realized that consumables don't even have a rarity or description attribute, like weapons and other items do, I decided to create yet another base class which would only have a name attribute. This would be the new "class Item" and the generic class I had before was now called "DetailedItem" (with rarity and description attributes) and derived from Item. I thought it would be a trivial change, but now g++ gives me such errors "error: cannot dynamic_cast ‘(const Item&)(+ i)’ (of type ‘const class Item&’) to type ‘const class Weapon&’ (source type is not polymorphic)". I guess it has to do with the fact that class Weapon is derived from DetailedItem, which is derived from Item on the other hand. This is the code which triggers the error:
// main.cpp
// Output function for generic Item class and its derived, specialized classes
ostream& operator<<(ostream& os, const Item&);
ostream& operator<<(ostream& os, const Weapon&);
ostream& operator<<(ostream& os, const Consumable&);

// Generic item class should cast to correct class and call that class' output function
ostream& operator<<(ostream& os, const Item& i) {
	os << i.Name();

	switch(i.ItemType()) {
		case Item::WEAPON: {
		// Call operator<<(ostream& os, const Weapon&) here
		const Weapon& weapon = dynamic_cast<const Weapon&>(i); // This line fails
		os << weapon;
		}
		break;

		case Item::CONSUMABLE: {
		const Consumable& consumable = dynamic_cast<const Consumable&>(i); // Fails aswell
		os << consumable;
		}
		break;

		default:
		os << " (Unhandled item type)";
	};
	return os;
}

int main() {
	// Create a character inventory to hold items of base class "Item". These can be "Consumable", "Weapon", etc.
	// The items will be stored in an std::vector<Item> in the "Inventory" class
	Inventory inventory;

	Weapon weapon = Weapon("Sange", "AUW 1963 model. Made by Jou'un.", Weapon::KATANA, 10, 0);
	weapon.SetATP(500, 550);
	weapon.SetATA(50);
	Consumable consumable = Consumable("Monomate", "Health recovery fluid. Restores HP. Restoration is based upon difficulty level");
	Mag mag = Mag("Sato", 5, 145, 45, 5);

	inventory.Add(weapon);
	inventory.Add(consumable);
	inventory.Add(mag);


	// print whole inventory using the specialized output operator<<
	for(Inventory::const_iterator i = inventory.begin(); i != inventory.end(); ++i) 
		std::cout << *i << std::endl;
}

// inventory.hpp
class Inventory {
public:
	static const unsigned MAX_ITEMS = 30;

	Inventory();
	bool Add(const Item& item);

	// STL-ish container access
	typedef std::vector<Item>::const_iterator const_iterator;
	typedef std::vector<Item>::iterator iterator;
	iterator begin() { return items.begin(); };
	iterator end() { return items.end(); };

	// ... snip ...

private:
	std::vector<Item> items;
};

// item.hpp
class Item {
public:
	enum Type { CONSUMABLE, WEAPON, ARMOR, BARRIER, SHIELD, MAG, SLOT };
	Item(const string& name, const Type& type);
	~Item() {}

	string Name() const;
	Type ItemType() const;
private:
	string name;
	Type type;
};

// detaileditem.hpp
class DetailedItem : public Item {
public:
	DetailedItem(const string& name, const Type& type, const string& description, const unsigned char& rarity);
	~DetailedItem() {}
	string Description() const;
	int Rarity() const;
private:
	string description;
	unsigned char rarity; // [0;12]
};

// consumable.hpp
class Consumable : public DetailedItem {
public:
	Consumable(const string& name, const string& description,  const unsigned& amount = 1);

	int Amount() const;
private:
	unsigned char amount;
};

// etc.

I'm kind of lost with the error message, because I don't see how my classes are unrelated to each other. Any help would be nice! Thanks in advance :) [Edited by - Eskapade on December 25, 2008 6:48:36 PM]

Share this post


Link to post
Share on other sites
Advertisement
Inheritance and dynamic types can only be used with pointer and reference types, not value types. So your inventory vector is not polymorphic; it can only and will store only the Item part of anything you put into it, and anything you put into it will become an Item and lose their dynamic type. This is known as slicing, as only the parent-slice of the derived class survives.

Since dynamic types can only be used with pointer and reference types, and references cannot be stored in the vector, your inventory needs to store pointers to Items.

std::vector<Item *> items;

Then allocate all items dynamically.

Share this post


Link to post
Share on other sites
Thanks Brother Bob, that did the trick. I think I won't use std::vector but a normal array then, because I can't seem to get the syntax readable with vectors..

Thanks to everyone who gave my problem a try, happy holidays :)

Share this post


Link to post
Share on other sites
You can probably use a typedef to clean up the syntax of storing pointers in a vector. While you're at it, use a typedef to store smart pointer classes in a vector. Or just use a typedef for the whole vector type, like typedef std::vector<Item*> item_vector.

Share this post


Link to post
Share on other sites
Quote:
Original post by theOcelot
You can probably use a typedef to clean up the syntax of storing pointers in a vector. While you're at it, use a typedef to store smart pointer classes in a vector. Or just use a typedef for the whole vector type, like typedef std::vector<Item*> item_vector.


That's not the problem, I'm worried about the interface from the outside. When iterating through the items with pointers I'd have to use (in STL-style with iterators)


for(Inventory::const_iterator i = inventory.begin(); i != inventory.end(); ++i)
std::cout << **i << std::endl;



In my opinion the ** is just confusing in this case.

Share this post


Link to post
Share on other sites
That is a pretty small worry. If you really want to, you can use operator[] when iterating over a vector.

Or you can give a name to the value, if that helps you read the code:

for(Inventory::const_iterator i = inventory.begin(); i != inventory.end(); ++i)
{
const Item &item = **i;
std::cout << item << std::end
}


Or:

for(Inventory::const_iterator i = inventory.begin(); i != inventory.end(); ++i)
{
const Item *item = *i;
std::cout << *item << std::end
}


Share this post


Link to post
Share on other sites
Or, alternatively, you can simplify that type of iteration further by using Boost.Foreach:


#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

foreach(Item* i, inventory)
std::cout << *item << std::endl;


Share this post


Link to post
Share on other sites
Seems like I should really delve into Boost after all.

To clarify: the syntactic PITA with that are things like (*iterator)->Method(), the necessary paranthesis around the iterator just turned me off :)

Thanks for the great hints!

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!