Interacting with a vector of objects derived from base class

I am currently trying to wrap my head around how to actually work with derived / inherited classes, how to set up a little "tree" of such classes and how to store and access them in a vector.

Like for a game where the player can have different types of items in their inventory but they are all stored in one vector and not a bunch of vectors for each item type.

I know you can do this with a vector of pointers to the base object and then use dynamic_cast to cast the pointers to be pointers to objects of the appropriate class.

I know that you have to be VERY careful with this and you have to make a second vector that keeps track of what object type is stored at which index to cast them properly.

My question is:
What is the proper way of implementing something like this?
I have read "don't do the dynamic cast thing" online but just saying "don't do this" without an alternate or better solution is not really useful ...

Here is a little example of what I am trying to implement:
(Without boundschecking, without type checking for the casting, just to get across what I want to do)

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 <vector>
#include <string>
#include <memory>

using namespace std;


//START OF ITEM CLASSES

class Item{
public:
	string Name;		//Each item has a name, no matter what type of item it is
	float Value;		//Each item has a value when buying or selling it

	void Use(void)
		{
			//Default function that gets called if no actual use function is implemented (if this does not get overloaded)
			cout << "Can't use this item" << endl;
		}
};

class Tool: public Item{
public:
	float DamagedState;		//Tools get damaged over time when used


	//No overloaded "Use" function here because there won't be any actual objects of this class, but functions might take objects of this type as parameters

	Tool(void)
		{

		}
};

class Hammer: public Tool{
public:
	Hammer(void)
		{
			Name = "Hammer";
		}
	virtual void Use(void)
		{
			cout << "Hammertime!" << endl;
			DamagedState = DamagedState - 0.1;
		}
};

//END OF ITEM CLASSES




class Person{
public:
	string Name;

	vector<unique_ptr<Item>> Inventory;

	Person(string CName)
		{
			Name = CName;
		}

	void UseTool(Tool ToolToUse)
		{
			//ToolToUse can be something from the players inventory or something from the gameworld

			cout << Name << " is now using " << ToolToUse.Name << endl;
			ToolToUse.Use();
			ToolToUse.DamagedState = ToolToUse.DamagedState - 0.1;
		}
	void PickUpItem(Item ItemToPickUp)
		{
			//Could be any item: a tool, a piece of clothing, a weapon, a food item, ...

			//Or is it better to make like ten different overloaded functions, one for each type of item, so like PickUpItem(Tool ItemToPickUo) PickUpItem(Clothing ItemToPickUp) ... ?

			Inventory.push_back(????????);		//How do I push back any object? First cast them to a pointer to the base object?
		}
	Item DropItem(int IndexOfItemToDrop)
		{
			//Removes an item from the players inventory and retuns it (the returned object will be added to a vector of items on the ground or whatever)
			//The main concept I need to figure out for this is how to return an item of any sub class
			
			return ??? Inventory[IndexOfItemToDrop] ???;
		}
};

int main()
{

	Person Player("Player");		//There is a person object called "Player"

	Hammer MyOldHammer();		//There is a hammer object called MyOldHammer

	Player.PickUpItem(??? MyOldHammer ???);		//Adding the Hammer object to the players inventory, but I need to cast it somehow first?

	Player.UseTool(??? Player.Inventory[0] ???);	//How do I retreive a certain index from the vector like Player.Inventory[i] dereference it and then cast it to be a "Tool" object?
	
	vector<unique_ptr<Item>> ItemsOnTheGround;
	
	ItemsOnTheGround.push_back(??? Player.DropItem[0] ???);		//Again, adding any object derived from the base objec

    return 0;
}
Inheritance works best if all classes share the same interface that is used everywhere. Then you can use the base class to define the interface (typically as virtual functions) and let each derived class implement the virtual functions differently.

Something like this:

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
#include <iostream>
#include <vector>
#include <string>
#include <memory>

class Item
{
public:
	virtual std::string getName() const = 0;
	virtual void use()
	{
		std::cout << "Can't use this item\n";
	}
	virtual ~Item() = default;
};

class Tool : public Item
{
protected:
	float damagedState = 1.0f;
};

class Hammer : public Tool
{
public:
	std::string getName() const override
	{
		return "Hammer";
	}
	void use() override
	{
		std::cout << "Hammertime!\n";
		damagedState = damagedState - 0.1;
	}
};

class Person
{
private:
	std::string name;
	std::vector<std::unique_ptr<Item>> inventory;

public:
	Person(const std::string& name)
	:	name(name)
	{
	}
	void useItemAtIndex(std::size_t index)
	{
		Item& itemToUse = *inventory[index];
		std::cout << name << " is now using " << itemToUse.getName() << "\n";
		itemToUse.use();
	}
	std::size_t pickUpItem(std::unique_ptr<Item> itemToPickUp)
	{
		inventory.push_back(std::move(itemToPickUp));
		return inventory.size() - 1;
	}
	std::unique_ptr<Item> dropItem(std::size_t indexOfItemToDrop)
	{
		std::unique_ptr<Item> itemToDrop = std::move(inventory[indexOfItemToDrop]);
		inventory.erase(inventory.begin() + indexOfItemToDrop);
		return itemToDrop;
	}
};

int main()
{
	Person player("Player");
	std::size_t myOldHammerIndex = player.pickUpItem(std::make_unique<Hammer>());
	player.useItemAtIndex(myOldHammerIndex);
	std::vector<std::unique_ptr<Item>> itemsOnTheGround;
	itemsOnTheGround.push_back({player.dropItem(myOldHammerIndex)});
}


Note the changes:
* I made the member variables of the Person class private as is usually the norm in OOP.
* I use unique_ptr when passing ownership of items to functions.
* I changed pickUpItem to return the index of the added item in the inventory. This is so that you have some way of referring to the item in the inventory but I'm not sure this is the best way. If you drop other items before the item the index will no longer be valid so you need to be careful.
* I changed UseTool to useItemAtIndex so that it uses index to refer to the item and to avoid any special handling of tools.

I don't claim this is necessarily a good design, it depends on how it's intended to be used, but at least it's closer to what I would expect and write myself.

If you find that your classes need widely different interface, you might want to question if inheritance is the right tool for the job. Sometimes using data to represent things is just easier even if it means some member variables are unused for some objects.
Last edited on
Consider this alternative design that uses a simple struct instead of a class hierarchy:

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
#include <iostream>
#include <vector>
#include <string>

struct Item
{
	std::string name;
	float damagedState = 1.0f;
	
	bool canBeUsed = false;
	float useWear = 0.0f;
	std::string useText;
};

Item createHammer()
{
	Item hammer;
	hammer.name = "Hammer";
	hammer.canBeUsed = true;
	hammer.useWear = 0.1f;
	hammer.useText = "Hammertime!";
	return hammer;
}

class Person {
private:
	std::string name;
	std::vector<Item> inventory;

public:
	Person(const std::string& name)
	:	name(name)
	{
	}
	void useItemAtIndex(std::size_t index)
	{
		Item& itemToUse = inventory[index];
		if (itemToUse.canBeUsed)
		{
			std::cout << name << " is now using " << itemToUse.name << "\n";
			std::cout << itemToUse.useText << "\n";
			itemToUse.damagedState -= itemToUse.useWear;
		}
		else
		{
			std::cout << "Can't use this item\n";
		}
	}
	std::size_t pickUpItem(Item itemToPickUp)
	{
		inventory.push_back(itemToPickUp);
		return inventory.size() - 1;
	}
	Item dropItem(std::size_t indexOfItemToDrop)
	{
		Item itemToDrop = std::move(inventory[indexOfItemToDrop]);
		inventory.erase(inventory.begin() + indexOfItemToDrop);
		return itemToDrop;
	}
};

int main()
{
	Person player("Player");
	std::size_t myOldHammerIndex = player.pickUpItem(createHammer());
	player.useItemAtIndex(myOldHammerIndex);
	std::vector<Item> itemsOnTheGround;
	itemsOnTheGround.push_back(player.dropItem(myOldHammerIndex));
}
Last edited on
Registered users can post here. Sign in or register to post.