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
70
#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
//ToolToUse can be something from the players inventory or something from the gameworld

I failed to consider this above.

Well, what you could do is change
 
void useItemAtIndex(std::size_t index)
to
 
void useItem(Item& itemToUse)
and add a getItemAtIndex function that returns an Item& (assuming you don't want the inventory to be public) that you can use to access the item that you want to pass to useItem.
 
player.useItem(player.getItemAtIndex(myOldHammerIndex));

Note that unique_ptr is not used for the itemToUse parameter because we don't want to transfer ownership of the object. Read more: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f7-for-general-use-take-t-or-t-arguments-rather-than-smart-pointers

Also note that the item object is still owned by the player so if the item is dropped and destroyed the reference returned from getItemAtIndex will no longer be valid to use.
Last edited on
Thanks for the quick answer!

The solution of your first answer is what I was looking for.
It didn't work right aways because of some issues with the virtual destructor.
But I managed to get it to compile.

Except for the pickUpItem() method in the Person class:
The compiler says that a parameter of an abstract type is not allowed as a function parameter.
Since the base class doesn't have to be virtual could I just make it non-virtual and it should work fine?
As far as I understand it the whole point of virtual classes is just to make sure that every derived class actually implements the methods declared in the base class and to make sure nobody creates an actual object from the class.


And the inventory.push_back(itemToPickUp) line in the pickUpItem method of the Person class:
Shouldn't that be a pointer to the object to pick up, because the inventory vector stores pointers?
Last edited on
It didn't work right aways because of some issues with the virtual destructor.

The destructor of a base class needs to be virtual otherwise it won't call the derived destructor when deleted through a base class pointer which is what happens when using unique_ptr.

The compiler says that a parameter of an abstract type is not allowed as a function parameter.

The = 0; at the end of the virtual functions in my example makes them pure virtual. A class with one or more pure virtual functions is an abstract class. You cannot create objects of an abstract class. A derived class that inherits from an abstract class needs to implement all the pure virtual functions otherwise that class will also be an abstract class.

The only abstract class in my example is Item. You shouldn't need to create any objects of type Item (only of classes derived from Item). Make sure you are actually passing the object as unique_ptr<Item> as in my first example. You don't want to pass Item by value as in my second example when dealing with inheritance as that would try to create an object of type Item which is not possible because it is abstract and it's not what you want anyway (if it wasn't abstract it would lead to "object slicing" which is bad).

https://en.wikipedia.org/wiki/Object_slicing

Since the base class doesn't have to be virtual could I just make it non-virtual and it should work fine?

If the functions are not marked virtual in the base class it won't call the derived version when called through a base class pointer or reference.

And the inventory.push_back(itemToPickUp) line in the pickUpItem method of the Person class:
Shouldn't that be a pointer to the object to pick up, because the inventory vector stores pointers?

This code is for the second example which does not store pointers, so no.
Last edited on
Ah ok, got it.

All of this virtual stuff is new to me and I have picked up a few bits of knowledge from here and there but I haven't got the "full picture" yet, but I am getting there, slowly ...

Your code actually works perfectly fine, it was my mistake, I copy&pasted it and changed a few things before compiling, hence the errors.

I always assumed that the inherited method calling worked kind of like overloading functions but things are not as simple as I thought.

i mean it kind of works like that when working with objects and methods directly, which I have been doing so far (and I could have solved this problem with an "inventory" class that contains vectors for each possible object but I thought this could be a good practical reason to finally start messing around with pointers, I have been able to avoid that topic so far for my personal projects).

And yes, no item objects are ever created, the dropItem method returns a pointer to an item.

The whole concept of just using pointers feels a bit weird to me because when you first learn about pointers it is through stuff like "there is a variable int var = 2" and "here is a pointer that points to it", which makes sense because the variable var gets created right there.
But when doing something like calling a constructor and just using the pointer to the created object I always think "where is the actual object" because there is no direct way of accessing it and it just exists somewhere in "not directly accessible" memory and not in a global variable or 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.


Do you know about variant types? You can have a vector of variants.

https://en.cppreference.com/w/cpp/utility/variant.html
https://www.cppstories.com/2018/06/variant/
https://www.cppstories.com/2020/04/variant-virtual-polymorphism.html/
Registered users can post here. Sign in or register to post.