Class Inheritance questions

Hello everyone. I'm messing around with an inventory system for a console game, and have gotten pretty close to what I need but am running aground on some finer points. I have a base item class, and a weapon class which is derived from it. The weapon class has "int attack", which other items won't have (for example, I'll have an item class for potions etc, and armor class for defensive stuff.) Now, I can initialize the base class and derived class no problem. But READING the derived class values is my issue.

I have a std::list<BaseItem> which is housing my weapons. I believe this is why I can't access my Weapon class' member "getAttack()", since the list is of type BaseItem. So, this begs the question, if I want an inventory system with BaseItems (remember I'll have Weapon subclass, and armor subclass) how can I access their individual attack or defense parameters? I've included the relevant code below. Thanks for any pointers!

BaseItem.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  #include <string>

class BaseItem
{
public:
	BaseItem();
	BaseItem(std::string name, std::string desc, int id, int value, int weight);

	// Getters and Setters
	std::string getName() { return name_; };
	std::string getDesc() { return desc_; };
	int getId() { return id_; };
	int getValue() { return value_; };
	int getWeight() { return weight_; };


private:
	std::string name_;
	std::string desc_;
	int id_;
	int value_;
	int weight_;
};



Weapon.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "BaseItem.h"

class Weapon : public BaseItem
{
public:
	using BaseItem::BaseItem;
	Weapon(int attack, std::string name, std::string desc, int id, int value, int weight);

	// Getters and Setters
	int getAttack() { return attack_; };

private:
	int attack_;
};



Weapon.cpp
1
2
3
4
5
6
7
8
#include "Weapon.h"


Weapon::Weapon(int attack, std::string name, std::string desc, int id, int value, int weight)
	: BaseItem(name, desc, id, value, weight)
{
	attack_ = attack;
}




My inventory class has a private list of type BaseItem.
std::list<BaseItem> inventory_;

Now, as I said, I can access BaseItem's members no problem. But as soon as I try and access getAttack(), I get a "Class 'BaseItem' has no member 'getAttack'" which makes sense to me. After all, I have a list of type BaseItem, not of type Weapon. So I'm not quite sure how to structure it if I can't access my derived members from a BaseItem list.
Last edited on
A list of BaseItems can only hold objects of type BaseItem. If you try to insert a Weapon into the list the object will get sliced so that only the BaseItem part of the Weapon gets stored in the list.

If you want to be able to store any class object that inherits from BaseItem inside the list you will have to store pointers (preferably smart pointers), but this will still not allow you to access the getAttack() function through the BaseItem pointer because it wouldn't be type safe.

What you could do is let BaseItem define the interface that you want all items to have by using virtual functions that the other item classes can override to customize their behaviour.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BaseItem
{
public:
	virtual int getAttack() { return 0; }
};


class Weapon : public BaseItem
{
public:
	int getAttack() override { return attack_; }
private:
	int attack_;
};

This means all item classes will have a getAttack() function. Items that does not override the getAttack() function will get the default one defined in BaseItem that simply returns 0. If you need to check if an item is a weapon you could check if getAttack() returns 0, or you might want to have another virtual function isWeapon() that checks to see if the item is a weapon.

If all that the virtual functions do is specifying some value it might be easier to simply use a single Item class without inheritance.
Last edited on
Ah, that's the piece of the puzzle I was missing. Virtual functions. I haven't used/researched those yet. I also wondered about the 'slicing' of the object since the list itself is of a certain type. That definitely clears it up.

As for having a single item class, I wanted to be setup for any features I might think of in the future. This is only a personal learning project, nothing that'll be published or anything, so learning inheritance was the main goal with this.

In your weapon class, you have "int getAttack() override". Does the 'override' just specify that if the compiler comes across a member of the same name, that this member will take precedence? Thanks for your explanation!

Edit: Also, could you explain a little further on the type safe issue of using pointers?
Last edited on
Does the 'override' just specify that if the compiler comes across a member of the same name, that this member will take precedence?

Function overriding is when you define a function in a derived class with the same name, parameters and return type as a virtual function in the base class. When the function is called through a pointer (or reference) to the base class the version of the function that gets called depends on the object that the pointer points to.

1
2
3
4
Weapon weapon;
BaseItem* item = &weapon;
int x = item->getAttack(); // this calls Weapon::getAttack() because
                           // item points to a Weapon object 

The 'override' specifier isn't strictly necessary, but it's useful because it makes sure that we do actually override a function in the base class, otherwise it gives us an error.


Also, could you explain a little further on the type safe issue of using pointers?

Normally, the type system prevents you from doing things that doesn't make sense.

1
2
std::string str;
str.help(); // error: ‘std::string’ has no member named ‘help’ 

But if you would have been able to call getAttack() on a BaseItem pointer (or reference) even though BaseItem does not have such a function it would have been possible to write code that cannot work, because the objects are not guaranteed to have a getAttack() function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class BaseItem {};

class Weapon : public BaseItem 
{
public:
	int getAttack();
};

class Armour : public BaseItem 
{
public:
	int getDefence();
};

int main()
{
	Armour armour;
	BaseItem* item = &armour;
	int x = item->getAttack(); // This couldn't possibly work because
	                           // Armour doesn't have a getAttack() function.
}
Last edited on
Thank you very much Peter. I feel as though I'm getting closer to my goal, but I'm running into another issue. I made a virtual getAttack member in BaseItem which returns 0. I ran my program, and to my delight it finally compiled and allowed me to get to the screen which prints the attack value. Although, instead of printing weapon.h's attack_ variable, it's simply returning 0. This is because, as you brought up, my data member is being sliced once it's put into the BaseItem list.

This leads to Polymorphism, and what you mentioned about using pointers. (From my findings) it seems the only way to stop the slicing from occurring is through the use of polymorphism. So, I did just that. I turned my list into std::list<BaseItem*> inventory_, and push_backed &weapon. I also changed the iterator to a pointer iterator, and dereferenced when displaying the needed information.

I ran the program in normal mode and got to my inventory display, when the program crashed with a debug error, abort() has been called. I ran it in debug mode and am getting an unhandled exception: std::bad_alloc at memory location blahblah. This is happening at line 11.

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

class BaseItem
{
public:
	BaseItem();
	BaseItem(std::string name, std::string desc, int id, int value, int weight);


	// Getters and Setters
	std::string getName() { return name_; };
	std::string getDesc() { return desc_; };
	int getId() { return id_; };
	int getValue() { return value_; };
	int getWeight() { return weight_; };

	virtual int getAttack() { return 0; };

private:
	std::string name_;
	std::string desc_;
	int id_;
	int value_;
	int weight_;
};


So, now I'm stumped. Upon googling, some are saying there's a stack overflow issue. If that's the case, why did the non-pointer version work? I can print everything in BaseItem, I just can't access weapon's member because of the slicing. But with the pointers, I'm getting memory issues. Do I need to send the instantiated objects to the heap with 'new'? And if so, where do I define 'new'? If not, any idea on what I can try next?

EDIT!!
I've got it. When adding a new weapon to my list, I just needed to create a pointer first, then allocate the object to the heap. Then store the pointer in the list. Now I can access my getAttack member from the derived class while using the BaseItem class' members as well. Thank you so much for the help Peter. To anyone: I'm still curious as to why the non-pointer list still worked, but the pointer list created a memory leak?

1
2
3
4
5
void Inventory::addItem(int attack, std::string name, std::string desc, int id, int value, int weight)
{
	Weapon* test = new Weapon(attack, name, desc, id, value, weight);
	inventory_.push_back(test);
}
Last edited on
Local variables are destroyed when they go out of scope so that is probably why it didn't work at first.

1
2
3
4
5
6
7
{

	Weapon weapon(attack, name, desc, id, value, weight);
	inventory_.push_back(&weapon);

} // weapon is destroyed here. 
  // The list will now contain an invalid pointer that is not safe to use. 


As you have found out, the common solution to this problem is to allocate the object on the heap.
Last edited on
As an aside, while you now understanda bit more about the heap, here's a solid rule of thumb; don't use new, don't use raw pointers. Use modern smart pointers with make_unique
Using inheritance this way may affect your code in a bad way, problem as this may make bugs and errors very hard to be detected and unless you use some programs, such as checkmarx, it is going to spend a lot of your time.
Topic archived. No new replies allowed.