How to make an array of a parent class, made out of different subclasses

167 Views Asked by At

I have a class Item, and there are two classes derived from it Sword and Shield.

I want my player to have an array of items:

class Item
{
    int x;
};

class Sword : public Item
{
     int sharpness = 10;
};

class Shield : public Item
{
    int blockChance = 2;
};

class player 
{
    Item* items[4];
    player()
    {
        items[0] = new Sword;
        items[1] = new Sword;
        items[2] = new Shield;
        items[3] = new Shield;
    }
};

Is there a way I can access values of Sword or Shield from items, for example:

items[2]->blockChance;

without having a separate array of Swords and Shields

If this is not possible, then I would just have a separate array of each, but a single array containing both would make this much easier

3

There are 3 best solutions below

1
wohlstad On BEST ANSWER

In order to access the properties of a derived class via the base class, you can add a virtual method. This is a method a derived class can override to provide a different implementation. If the base class has no meaningful implementation for it, it can also be abstract (AKA pure-virtual), which will force the derived class to implement it.

There are several other issues you should be aware of:

  1. When dealing with inheritance and virtuals method, it is important to have a virtual destructor in the base class. Otherwise the destructor of the derived classed will not be invoked when they are access via a pointer/reference (even if in your toy example this is not relevant, it's a good practice).

  2. Instead of using raw C arrays, you should favor std::array for static sized arrays, and std::vector for dynamic sized ones.

  3. Instead of using raw pointers with manual new/delete, you should use smart pointers: std::unique_ptr (the default), or std::shared_ptr (if shared ownership is required).

  4. You can use a member initialization list to init items in the constructor of class Player.

The complete solution is demonstrated below:

#include <array>    // for std::array
#include <memory>   // for std::unique_ptr
#include <iostream> // for std::cout etc.

class Item
{
public:
    virtual ~Item() {}              // important to add a virtual destructor
    virtual int GetProperty() = 0;  // a virtual method for accessing the property
};

class Sword : public Item
{
    int sharpness = 10;
public:
    int GetProperty() override { return sharpness; }   // override of the virtual method
};

class Shield : public Item
{
    int blockChance = 2;
public:
    int GetProperty() override { return blockChance; } // override of the virtual method
};

class Player
{
    std::array<std::unique_ptr<Item>, 4> items; // use std::array instead of a raw C array (or std::vector for a dynamic array), 
                                                // and std::unique_ptr instead of a raw poniter.
public:
    Player() 
        : items{ std::make_unique<Sword>(), 
                 std::make_unique<Sword>(), 
                 std::make_unique<Shield>(), 
                 std::make_unique<Shield>() }   // use a member initialization list
          {}
    std::array<std::unique_ptr<Item>, 4>& GetItems() { return items; }
};

int main()
{
    Player player;
    std::cout << player.GetItems()[0]->GetProperty() << '\n';   // access the Sword property
    std::cout << player.GetItems()[2]->GetProperty() << '\n';   // access the Shield property
}

Output:

10
2

Live demo - Godbolt

0
user12002570 On

Is there a way I can access values of Sword or Shield from items

Yes, you can add a virtual method that returns the value of the property as shown below.

Note also that you should use smart pointers to avoid manual memory management(new/delete etc).

class Item
{
    int x;
    public:
    //add this virtual method  
    virtual int GetProperty()
    {
        return x;
    }
    virtual ~Item(){}
};

class Sword : public Item
{
     int sharpness = 10;
     public:
     //override and return property value
     int GetProperty() override
     {
        return sharpness;  
     }
};

class Shield : public Item
{
    int blockChance = 2; 
    public:
    //override and return property value
    int GetProperty() override
    {
        return blockChance;   
    } 
};

class player 
{   
    Item* items[4];
    player()
    {
        items[0] = new Sword;
        items[1] = new Sword;
        items[2] = new Shield;
        items[3] = new Shield;

        items[0]->GetProperty(); //get value
        items[2]->GetProperty(); //get value 
    }
};

Demo Demo using smart pointers

0
John Bayko On

When using inheritance an important thing to remember is the Liskov substitution principle, which says at the level of the parent class, every subclass must be indistinguishable from the parent - that is, no behaviour that contradicts the parent declaration. Normally you would cast down to the subclass (the subclass may have additional features, but not take away anything), but first you need to be able to identify in the parent class what subclass it really is. C++ has the typeof operator, which you can compare using ==, then dynamic_cast<>() to get to the actual class (e.g. assign to a pointer).

Another thing which can avoid casting is to use virtual methods, with an ability to check if it's implemented, e.g has_sharpness() with get_sharpness(), if has_sharpness() is true you know Item is a sword (or maybe axe) and you can use the value retuned by get_sharpness(). In this case all classes implement the same interface so no casting is needed, even though only some properties apply.