Vector with mixed class type?

I'm trying to write a basic RPG style battle system to practice classes, but I'm having a little trouble with creating an inventory system.

To keep things simple, I want to restrict the items to Weapons, Armour and Items.

Originally I thought of making a class Item, and instantiating all items from this class. But this appears to be quite messy, as the constructor for each item would take a dozen or so arguments, such as name, type, value, weight etc, and even messier again when you consider an item of "weapon" type might contain a variable for modifying damage, which is irrelevant to an item of type "potion" or "armour".

So, my next idea was to have separate classes for Weapons, Armour and Potions. That way member variables are relevant to the type of item, and each instance will only require a couple of arguments, which seems more manageable.

However, how can I wrap different classes up into an inventory within a Player class, so that items can be added, removed and listed? Is it possible to have a vector of different Class types? Is there a solution within class templates?

I'm not looking for anybody to write the code, just to point me in the right direction.

*EDIT*

I should probably go back to this post for anybody similarly stuck, and mention that using virtual functions let me achieve what I was trying to do. MiiNiPaa posted an example here: http://www.cplusplus.com/forum/beginner/142695/
Last edited on
You may want to look into polymorphism, http://www.cplusplus.com/doc/tutorial/polymorphism/ where each type of item has the same base class.

This is a perfect situation for using inheritance. Your Weapon, Armour, etc. classes can all be specialisations of a base Item class. Your inventory can then be a vector of pointers to Item. Polymorphism is your friend :)

I also vote for polymorphism as the solution. Keep in mind that you'll have to either make your data member names more generic or you'll have to use virtual "getter" and "setter" methods to access the data from the parent class.
Excellent, thanks guys...I love this site, could have been sitting around for hours frustrated, but instead got much further than expected! Inheritance makes complete sense. That is as far as I am in learning C++, so a little on the edge of my ability, but I did manage to get it roughly working.

A couple of quick questions...

MikeyBoy, you suggest a vector of pointers as opposed to a standard vector of the parent "Item" class I made. Is this just purely for performance reasons? Is there an extra copy being made, once when I declare the instance of a class, and again when I use push_back into the vector? Or am I fooling myself into thinking this works with the vector<class> I used?

I'll include the main code of what I ended up with. The derivative classes Weapon, Armour and Potion are declared/defined separately.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "Header.h"


int main()
{

	std::vector<Item> inventory;

	Weapon sword("Sword", "A basic steel sword", 100, 1, 2);
	Armour leather("Leather Armour", "A set of leather armour", 50, 2, 0);
	Potion healthPotion("Health Potion", "Regenerates health", 10, 20, 0);

	inventory.push_back(sword);
	inventory.push_back(leather);
	inventory.push_back(healthPotion);

	std::vector<Item>::iterator it = inventory.begin();

	for (; it != inventory.end(); ++it)
		std::cout << it->m_name << "\t" << it->m_description << "\t" << it->m_value << std::endl;


Also, when writing the constructors for the derivative class definitions, I noticed that I was continually having to define the shared variables for each one; m_name, m_description and m_value (see code below).

Is there some way that this does not have to be done? That the derivative class constructors only require the variables that are unique to them, and somehow automatically include the parent's constructor?

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
#include "Header.h"

Item::Item()
{
	m_name = "Default";
	m_description = "Default";
	m_value = 1;
}

Item::Item(std::string name, std::string desc, int value)
{
	m_name = name;
	m_description = desc;
	m_value = value;
}


Weapon::Weapon(std::string name, std::string desc, int value, double hitMod, int numDice)
{
	m_name = name;
	m_description = desc;
	m_value = value;
	m_hitMod = hitMod;
	m_numDice = numDice;
}

Armour::Armour(std::string name, std::string desc, int value, double phyRed, double magRed)
{
	m_name = name;
	m_description = desc;
	m_value = value;
	m_phyRed = phyRed;
	m_magRed = magRed;
}

Potion::Potion(std::string name, std::string desc, int value, int gainHP, int gainMP)
{
	m_name = name;
	m_description = desc;
	m_value = value;
	m_gainHP = gainHP;
	m_gainMP = gainMP;
}
You can call the base constructor.

1
2
3
4
5
Weapon::Weapon(std::string name, std::string desc, int value, double hitMod, int numDice) : Item(name, desc, value)
{
    m_hitMod = hitMod;
    m_numDice = numDice;
}
MikeyBoy, you suggest a vector of pointers as opposed to a standard vector of the parent "Item" class I made. Is this just purely for performance reasons? Is there an extra copy being made, once when I declare the instance of a class, and again when I use push_back into the vector? Or am I fooling myself into thinking this works with the vector<class> I used?

The reason you want pointers, is that if you define a vector of Item objects, then what you get in your vector is exactly that: objects of type Item, not objects of type Armour, Potion, etc. When you attempt to copy your Potion into the vector, all that you will have in that vector is the Item part of the original object, not the whole thing. This is known as object slicing:

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

Also, when writing the constructors for the derivative class definitions, I noticed that I was continually having to define the shared variables for each one; m_name, m_description and m_value (see code below).

Is there some way that this does not have to be done? That the derivative class constructors only require the variables that are unique to them, and somehow automatically include the parent's constructor?

Giblit's suggestion is spot-on.

Also, the fact that your derived class constructors are directly accessing the data members of your base class means that you haven't made those data members private. That's something to avoid as much as possible - non-private data members will compromise the encapsulation of your class.
Thanks a lot for the reply MikeyBoy. Not using private was really just for the sake of simplifying the problem for myself.

I had never heard of object slicing before, so thanks for bringing that to my attention. I simplified the problem further to check this idea out, and was hoping you could take a quick look at it. I think object slicing is exactly what is happening here, though I can't find the cause.

In the code below, I have a base class Item, and a derived class Weapon. Item holds a string name, and Weapon holds an int damage value, as well as the inherited string name.

I have a vector of pointers pointing to type Item called inventory. I've populated inventory with 2 Items class and 1 Weapon class.

For practice, I'm attempting to read the contents of the classes through both pointers and iterators.

As you can see in the code, with an iterator I get the first string value, but then blank/garbage when I try to move the iterator up one. Why is this? I presume my syntax for it+1 is wrong, as even if I try it+999 I do not get an out of bounds error.

And as you can see, when I use a pointer, I can access the inherited string in all the classes, but can't access the Weapon's int damage value. This is object slicing? What is the reason?

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

class Item
{
public:
	Item(){}
	Item(std::string name) { m_name = name; }
	std::string m_name;
};

class Weapon : public Item
{
public:
	Weapon(std::string name, int damage);
	int m_damage;
};

Weapon::Weapon(std::string name, int damage) : Item(name)
{
	m_damage = damage;
}

int main()
{
	std::vector<Item*> inventory;
	
	inventory.push_back(new Item("Object 1"));
	inventory.push_back(new Item("Object 2"));
	inventory.push_back(new Weapon("Sword", 32));

	std::vector<Item*>::const_iterator it = inventory.begin();
	std::vector<Item*> *ptr = &inventory;
	
	std::cout << "inventory[0] " << (*it)->m_name << std::endl;
	std::cout << "inventory[1] " << (*it + 1)->m_name << std::endl;

	std::cout << std::endl;

	std::cout << "inventory[0] " << (*ptr)[0]->m_name << std::endl;
	std::cout << "inventory[1] " << (*ptr)[1]->m_name << std::endl;
	std::cout << "inventory[2] " << (*ptr)[2]->m_name << std::endl;

}

To access the damage you would have to downcast IIRC.

You can use dynamic casting and if it can't cast then it will return it as null.

http://www.cplusplus.com/doc/tutorial/typecasting/
Thanks giblit. I'll check that out.
An alternate thing to have would be some way of keeping track of what KIND of item it is. You could just do this with an enum, and then you would only require static_cast rather than dynamic_cast, due to already knowing for sure what kind of item it is. This has better performance, due to being a compile-time cast rather than a run-time cast, and also means you don't require RTTI.

As an example of how you might do 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
#include <vector>

class Item {
    public:
        enum class Type {
            Item,
            Weapon,
            Armour
        };

        Item(Type type = Type::Item) : _type(type) {}

        Type getType() const { return _type; } 

    private:
        const Type _type;
};

class Weapon : public Item {
    public:
        Weapon() : Item(Type::Weapon) {}
};

class Armour : public Item {
    public:
        Armour() : Item(Type::Armour) {}
};

int main() {
    std::vector<Item*> items;
    items.push_back(new Item);
    items.push_back(new Armour);
    items.push_back(new Weapon);

    for (Item* i : items) {
        if (i->getType() == Item::Type::Item) {
            // just a basic item, no downcasting required
        } else if (i->getType() == Item::Type::Weapon) {
            Weapon* w = static_cast<Weapon*>(i);
            // we know it is a weapon, so we can use it as such
        } else if (i->getType() == Item::Type::Armour) {
            Armour* a = static_cast<Armour*>(i);
            // we know it is an armour, so we can use it as such
        }
    }

    for (Item* i : items) {
        delete i; 
        // Don't forget to delete them when you are done!
        // I would suggest using smart pointers instead, in this case.
    }
}
Last edited on
You can also use a double dispatch pattern and avoid casting entirely.

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

struct Display;

struct Item
{
  Item( const std::string& n ):name(n){}
  std::string name;

  virtual Display display( void ) const;
};

struct Weapon : public Item
{
  Weapon( int d ): Item( "sword" ), damage(d) {}
  int damage;

  Display display( void ) const;
};

struct Armor : public Item
{
  Armor( int a ): Item( "shield" ), absorbtion( a ){}
  int absorbtion;

  Display display( void ) const;
};

struct Display
{
  std::string text;

  Display( const Item& i )
    : text( "Item: " + i.name )
  {
  }

  Display( const Weapon& w )
    : text( "Weapon: " + w.name + " Damage: " + std::to_string(w.damage) )
  {
  }

  Display( const Armor& a )
    : text( "Armor: " + a.name + " Absorbtion: " + std::to_string( a.absorbtion ) )
  {
  }

  void show( void ) const { std::cout << text << '\n'; }
};

Display Item::display( void )   const { return Display( *this ); }
Display Weapon::display( void ) const { return Display( *this ); }
Display Armor::display( void )  const { return Display( *this ); }

int main( void )
{
  Item   item( "tooth pick" );
  Weapon weapon(10);
  Armor  armor(5);
  std::vector< Item* > items = { &item, &weapon, &armor };
  for ( const Item* item : items )
    item->display().show();


  return 0;
}
Last edited on
Topic archived. No new replies allowed.