Different classes in same vector

Hello everyone!

I am currently working on a very primitive game (Which I'm making mainly to improve my understanding of C++).

I've recently ran into problems when attempting to program an inventory. My inventory consists of a vector<item> in which all items are put. The problem of course being that I have subclasses of items (weapons, shields, etc), and I'm having some problems doing this correctly. I've done some googling on the subject and something like this is what I imagine has to be done:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class item{
    public:
string name;
};

class weapon : public item{
    public:
    int damage;
};

class shield : public item{
    int armour;
};

int main()
{

vector<item*> inventory;
weapon *sword = new weapon;
sword->name = "Hello";
sword->damage = 10;
inventory.push_back(sword);

inventory[0]->name;
inventory[0]->damage;

}


The call to inventory[0]->name works, but the call to inventory[0]->damage does not. (Class 'item' has no member named 'damage'). I'm a little bit confused as to how I'm supposed to get ahold of the member damage in the class sword. I'm guessing it's supposed to be done through virtual functions or something of the like, which means the value has to be defined also in the master class, and not only in the weapon subclass. This confuses me because this then kind of defeats the purpose, since for example the class "weapon" will also have a value called "armour".

Any help understanding what i'm doing wrong here would be greatly appreciated!
inventory[0] is seen as an item *, and items don't have a damage data member.

So you'll have to downcast inventory[0] to a weapon *, by using dynamic_cast.
dynamic_cast won't work unless you have at least one virtual function in item.
You can add an empty virtual destructor to item (it's good practice for inheritance anyway).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <string>
#include <vector>
#include <iostream>

using namespace std;

class item{
	public:
string name;

virtual ~item() {}
};

class weapon : public item{
	public:
    int damage;
};

class shield : public item{
    int armour;
};

int main()
{
	vector<item*> inventory;
	weapon *sword = new weapon;
	sword->name = "Hello";
	sword->damage = 10;
	inventory.push_back(sword);

	inventory[0]->name;
	//inventory[0]->damage;
	std::cout << (dynamic_cast<weapon *> (inventory[0]))->damage
		<< std::endl;
}
Last edited on
Thank you! This helped a lot. I have a few follup questions, however. I worked a way to get this done by simply having a virtual function called "getValue()" which would return the damage if the class was weapon and would return the armour if the class was shield. This would of course only work as long as there's only one value (or an even number of values) specific to each subclass. I instead went with your way, which worked out fine until I tried using smart pointers instead of regular pointers (as I have been told that is a good idea).

I attempted using the shared_ptr pointer, but then when I try to do the dynamic cast I get the error message:
cannot dynamic_cast 'inventory.std::vector<_Tp, _Alloc>::operator[] [with _Tp = std::shared_ptr<item>, _Alloc = std::allocator<std::shared_ptr<item> >](0u)' (of type 'class std::shared_ptr<item>') to type 'class weapon*' (source is not a pointer)|

I guess i'm lacking some fundamental understanding on how this works, which is why im running into these problems (that im finding somewhat difficult to troubleshoot on my own).

edit: I also attempted using dynamic_pointer_cast instead of dynami_cast in which I got the error message: class std::shared_ptr<std::shared_ptr<weapon> >' has no member named 'damage'

which confuses me even further.
Last edited on
Please post some code next time, so your mistakes can be found...
At this point, all I can say is "works for me".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <string>
#include <vector>
#include <iostream>
#include <memory>

using namespace std;

class item{
	public:
string name;

virtual ~item() {}
};

class weapon : public item{
	public:
    int damage;
};

class shield : public item{
    int armour;
};

int main()
{
	vector<shared_ptr<item> > inventory;
	shared_ptr<weapon> sword(new weapon);
	sword->name = "Hello";
	sword->damage = 10;
	inventory.push_back(sword);

	inventory[0]->name;
	//inventory[0]->damage;
	std::cout << dynamic_pointer_cast<weapon> (inventory[0])->damage
		<< std::endl;
}
They say downcasting is a sign of bad design.
http://en.wikipedia.org/wiki/Downcasting
http://java.dzone.com/articles/defining-tell-dont-ask-well

And look at your code... how are you going to tell which element of the inventory you have to downcast, and to what?

I'd suggest you try to redesign your code, so that you never have to cast anything.
Thank you Catfish2. You've been extremely helpful. I'm sorry for not posting the code, I thought so little had changed it wasn't necesary but I see now that it was. As for downcasting, the entire question was related to how I could access the members of an inherited class in such a system.

I agree that it's somewhat difficult to tell which element that needs to be downcasted, but how would you suggest I could make an inventory system like this without doing it this way? Virtual functions?
Yes, I have virtual functions and polymorphism in mind, but I wouldn't keep items in the inventory at all.
What is an inventory in real life? Basically, it's a collection of information about things.

So I would store weapons, shields, etc. in their own separate containers. So that I know how I use them: as weapons, as shields?

Then you have the actual inventory, which is updated as needed, and only stores metadata, and not the items themselves.

I've chosen to have a virtual generateDescription() function that classes derived from Item can overload override as needed. The Inventory uses an std::map to associate an object's memory address with its description.
This probably isn't the best way to do things. But at least you know what your items are.

Source code below is C++11. I hope your compiler supports it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>

struct ItemDescription {
	std::string name;
	std::string trivia;

	ItemDescription() = default;

	ItemDescription(const std::string &name, const std::string &trivia):
		name(name),
		trivia(trivia)
	{
	}
};

class Item {
public:

	virtual ~Item()
	{
	}

	virtual ItemDescription generateDescription() const
	{
		return ItemDescription("Item", "Nothing interesting.");
	}
};

class Weapon: public Item {
public:

	Weapon(int damage, const std::string &name, const std::string &trivia):
		damage(damage),
		name(name),
		trivia(trivia)
	{
	}

	ItemDescription generateDescription() const
	{
		return ItemDescription(name, trivia + " It does " + std::to_string(damage) + " damage.");
	}

private:

	int damage;
	std::string name;
	std::string trivia;
};

class Inventory {
public:

	void addOrUpdate(const std::shared_ptr<Item> &spi)
	{
		m[spi.get()] = spi->generateDescription();
	}

	void erase(const std::shared_ptr<Item> &spi)
	{
		m.erase(spi.get());
	}

	void display() const
	{
		for (const auto &id: m) // auto == pair<key, data>
			std::cout << id.second.name << " *** " << id.second.trivia << '\n'<< std::endl;
	}

private:

	std::map<const Item *, ItemDescription> m;
};

int main()
{
	Inventory i;
	std::vector<std::shared_ptr<Item>> weapons;
	std::shared_ptr<Item> sword(new Weapon(100, "Durandal", "It's French."));
	std::shared_ptr<Item> gun(new Weapon(20, "M16", "It's an American rifle."));

	weapons.push_back(sword);
	weapons.push_back(gun);

	for (const std::shared_ptr<Item> &spi: weapons)
		i.addOrUpdate(spi);

	i.display();
	i.erase(gun);
	i.display();
}


Durandal *** It's French. It does 100 damage.

M16 *** It's an American rifle. It does 20 damage.

Durandal *** It's French. It does 100 damage.



Edit: cleaning up some stupid things.

Edit 2: I realized that the code can be simplified by associating objects' descriptions with their name instead of memory address. With the small inconvenience of not being able to have two objects named the same... but, no more smart pointers.

Edit 3: a bit less atrocious now, although ItemDescription is now totally useless and can be replace with a plain std::string. Important lesson here: strive to simplify your code. Preferably before you hit the Submit button.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <vector>

struct ItemDescription {
	std::string trivia;

	ItemDescription() = default;

	explicit ItemDescription(const std::string &trivia):
		trivia(trivia)
	{
	}
};

class Item {
public:

	Item(const std::string &name, const std::string &trivia):
		name(name),
		trivia(trivia)
	{
	}

	virtual ~Item()
	{
	}

	virtual ItemDescription generateDescription() const
	{
		return ItemDescription("Nothing interesting.");
	}

	std::string getName() const
	{
		return name;
	}

protected:

	std::string name;
	std::string trivia;
};

class Weapon: public Item {
public:

	Weapon(int damage, const std::string &name, const std::string &trivia):
		Item(name, trivia),
		damage(damage)
	{
	}

	ItemDescription generateDescription() const
	{
		return ItemDescription(trivia + " It does " + std::to_string(damage) + " damage.");
	}

private:

	int damage;
};

class Inventory {
public:

	void addOrUpdate(const Item &i)
	{
		m[i.getName()] = i.generateDescription();
	}

	void erase(const Item &i)
	{
		m.erase(i.getName());
	}

	void display() const
	{
		for (const auto &id: m) // auto == pair<key, data>
			std::cout << id.first << " *** " << id.second.trivia << '\n'<< std::endl;
	}

private:

	std::map<std::string, ItemDescription> m;
};

int main()
{
	Inventory i;
	std::vector<Weapon> weapons;
	Weapon sword(100, "Durandal", "It's French.");
	Weapon gun(20, "M16", "It's an American rifle.");

	weapons.push_back(sword);
	weapons.push_back(gun);

	for (const Weapon &spi: weapons)
		i.addOrUpdate(spi);

	i.display();
	i.erase(gun);
	i.display();
}

Last edited on
wow, thanks. This is infinitely helpful! Didn't even know std::map was a thing. Well that's embarresing.

Again, thanks!
Topic archived. No new replies allowed.