Array of inherited classes

I have an Inventory class in which I would like to create an array with objects from the Sword, Shield and Potion classes.

class Inventory {
public:
    Inventory();
    ~Inventory();
    virtual void add();
    Inventory** getinvent();
    void setinvent(Inventory** new_inventory);
    int getsize();
    void setsize(int new_size);
private:
    Inventory** inventory;
    int invent_size;
};

Inventory::Inventory() {
    inventory = new Inventory*[1];
    invent_size = 1;
}

class Sword : public Inventory {
public:
    Sword(int strength);
    ~Sword();
    void add();
private:
    int strength;
    Sword* sword;
};

Sword::Sword(int strength) {
    this->strength = strength;
    sword = this;
}

void Sword::add() {
    setsize(getsize() + 1);

    Inventory** new_invent = new Inventory*[getsize()];
    for (int i = 0; i < getsize() - 1; i++) {
        new_invent[i] = getinvent()[i];
    }

    new_invent[getsize() - 1] = sword;
    setinvent(new_invent);
}

      

The shield and potion classes are similar to the Sword class. If I make the following objects in implementation:

Inventory* inventory = new Inventory();
Sword* sword = new Sword(1);

      

How do I add this sword to this specific inventory? I don't think sword-> add (); will work as the sword does not know that it is inherited from the inventory. It's right? I tried to make the add () method virtual as it should work for sword, shield and potion objects.

+3


source to share


2 answers


Using dynamic polymorphism, we can create an abstract class Item

that describes the functionality that an item has in inventory. This is useful because with such a class it is possible to manage elements that we do not know about, we only know that they will behave like one.

class Item
{
public:
    virtual ~Item() = default;
    virtual const char* description() const = 0;
};

      

Further, all other items (swords, bottles, etc.) can inherit from this class, which gives them the property of being an element:

class Sword: public Item
{
public:
    Sword() = default;
    virtual ~Sword() = default;
    const char* description() const override
    { return "Sword"; }
};

      

In the method, description

it overrides the abstract text Item::description

, so whenever you call .description

from an instance Sword

, you get a string "Sword"

. For example:

Sword sword{};
Item& item = sword;
std::puts(item.description()); // prints the "Sword" string.

      

Now easier to store items, we just have to use their vector: std::vector<std::unique_ptr<Item>>

.

#include <vector>
#include <memory>

std::vector<std::unique_ptr<Item>> inventory{};
inventory.emplace_back(std::make_unique<Sword>());

      

But why can't we have std::vector<Item>

? Simply because it is impossible to build Item

from Sword

. In fact, it is impossible to even build Item

, because it has abstract methods (i.e. they only exist to describe the prototype of a method, not to define / implement it).

std::unique_ptr

is one of the few C ++ smart pointers, so it doesn't require manual allocation management. Using new

and delete

in your code can lead to memory leaks and disaster due to programmer distraction, so a smart pointer makes this problem irrelevant.

Finally, to get the item back, you can simply drop the item back to the Sword:

const auto& item = inventory[0]; // item is `const std::unique_ptr<Item>&`
puts(item->description()); // prints "Sword"
puts(dynamic_cast<Sword*>(item.get())->description()); // also prints "Sword"

      

The latter (using dynamic_cast) will create a converted pointer to that first element, from item.get()

but in the form Sword*

. You will want to do this if there is an item or data item from Sword

that is not shared by Item

. For example, if you have something like "int sword_power", you would do this:

auto sword = dynamic_cast<Sword*>(item.get());
if (sword != nullptr)
{
    std::printf("sword power: %d\n", sword->sword_power);
}

      

Of course, checking if the broadcast was successful is optional, but this prevents your code from performing undefined behavior (in case of broadcast failure and a null pointer return).

Another way to make this system ( not before C ++ 17) using a new library tool std::variant

.

Basically, a variant allows you to have one of many different types at a time. Unlike tuples, which allows you to have many different types (like struct), the variant only allows one value from one type at a time. To understand it better, here's how it works:

#include <variant> // C++17

struct Sword {};
struct Bottle {};

std::variant<Sword, Bottle> item = Sword{};

      



As well std::tuple

, an embodiment will have possible types of the template parameters as arguments (i.e., types Sword

and Bottle

are part of the whole Item

of all types). Thus, you can have either a sword or a bottle at a time, but never both at the same time . Let's implement our inventory with this new functionality. First we need to change our classes a little:

class Sword
{
public:
    int power;

    Sword() = default;
    const char* description() const
    { return "Sword"; }
};

class Bottle
{
public:
    bool empty;

    Bottle() = default;
    const char* description() const
    { return "Bottle"; }
};

      

We have removed the need for virtual methods and dynamic polymorphism, and you will also see that we no longer need dynamic allocation as it std::variant

is required to work on the stack (which means the program will also be faster (maybe)).

Now, for the sake of concept Item

, we make a variant alias with our classes:

using Item = std::variant<Sword, Bottle>;

      

And we can also use this with a vector:

std::vector<Item> inventory{};
inventory.emplace_back(Sword{});
inventory.emplace_back(Bottle{});

      

There are several ways to interact with these elements in case you need them. One of them is to use std::holds_alternative

:

auto& item = inventory[0];

if (std::holds_alternative<Sword>(item))
{
    auto& sword = std::get<Sword>(item);
    sword.power = 42;
    std::printf("%s: %d\n", sword.description(), sword.power);
}

      

It checks if the variant object supports the value of the type value. In this case, we checked for Sword

. Then, if there is a sword in there, we get the value using std::get<>

, which returns a reference to our element as Sword

.

Another way of accessing a real object is with std :: visit . To put it simply: Visitors are objects that behave like a function with overloads. You can call a visitor just like a function. To make a visitor, we can either use an overloaded operator()

s structure or lambdas. Here's the first approach:

struct VisitItem
{
    void operator() (Sword& sword) const
    {
        std::printf("%s: %d\n", sword.description(), sword.power);
    }

    void operator() (Bottle& bottle) const
    {
        std::printf("%s: %s\n", bottle.description(),
                    bottle.empty? "empty" : "full");
    }
};

auto& item = inventory[0];
std::visit(VisitItem{}, item); // we give an instance of VisitItem for std::visit, and the item itself.

      

Here std::visit

will output the correct one operator()

for the current object within the variant (i.e. element). If the element is holding the Sword, it is called operator() (Sword&)

.

Another approach is to make overloaded lambdas. It's a little more complicated as we don't have a library tool to do this, but with C ++ 17 it's easier to implement:

template <typename... Ts>
struct overload : Ts...
{
    using Ts::operator()...;
    template <typename... TTs>
    constexpr explicit overload(TTs&&... tts) noexcept
        : Ts{std::forward<TTs>(tts)}...
    {
    }
};

template <typename... Ts>
explicit overload(Ts&&...) -> overload<std::decay_t<Ts>...>;

      

And then use it like this:

auto& item = inventory[0];
auto visitor = overload(
    [] (Sword& s) { std::printf("power: %d\n", s.power); },
    [] (Bottle& b) { std::printf("%s\n", b.empty? "empty" : "full"); }
);

std::visit(visitor, item);

      

If you want to understand what's going on in the structure overload

, it inherits from all the lambdas you give it and transfers overloads operator()

to overloading (since function overloads from base classes are not considered candidates, so you have to using overload

). The line after overload

struct is a user-defined subtraction guide , which means you can change the template arguments of the template structure based on the constructor.

+1


source


It seems that you have accidentally assigned the same name to two completely different classes.

One class - "Item" - and "Sword" extends it.

class Sword: public Item {...};

      

Another class, Inventory, represents a list of items.

class Inventory
{
    void add(Item*) {...}
    ...
    Item** inventory;
};

      

Then you have to make sure that you only have one inventory and not one inventory per item. Adding material to this inventory should be easy.



Inventory* inventory = new Inventory();
Sword* sword = new Sword(1);
inventory->add(sword);

      


Note. You should avoid using new

and delete

. Use standard containers ( std::vector

) where possible. Also, use smart pointers ( std::unique_ptr

) whenever possible . Instead of a pointer to a pointer, use a list of smart pointers:

Item** inventory; // works, but not so good
std::vector<std::unique_ptr<Item>>; // better

      

This is a coding tip. This does not affect what the code actually does, but only there to reduce confusion (like where to put delete

what matches new

).

0


source







All Articles