继承类的数组

时间:2017-04-12 13:58:32

标签: c++ inheritance polymorphism

我有一个Inventory类,我想在其中创建一个包含Sword,Shield和Potion类对象的数组。

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);
}

Shield和Potion类与Sword类相似。如果我在实现中创建以下对象:

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

我现在如何将此剑添加到此特定广告资源?我不认为剑 - &gt; add();因为剑不知道它是从库存中继承的,所以会有用。它是否正确? 我试图将add()方法设为虚拟,因为它必须适用于剑,盾和药水对象。

2 个答案:

答案 0 :(得分:1)

使用动态多态,我们可以创建一个抽象类Item,它描述项目在清单中的功能。它很有用,因为通过这样的课程,我们可以管理我们不了解的项目,我们只知道它们会像一个人一样。

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

更进一步,所有其他物品(剑,瓶等)都可以从这个类继承,从而赋予它们作为物品的特征:

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

description方法中,它覆盖了Item::description抽象方法,因此,只要您从.description的实例中调用Sword,就可以使用"Sword"。将返回Sword sword{}; Item& item = sword; std::puts(item.description()); // prints the "Sword" string. 字符串。例如:

std::vector<std::unique_ptr<Item>>

现在更容易存储商品,我们只需要使用它们的向量:#include <vector> #include <memory> std::vector<std::unique_ptr<Item>> inventory{}; inventory.emplace_back(std::make_unique<Sword>());

std::vector<Item>

但为什么我们不能拥有Item?仅仅因为无法从Sword构建Item。实际上,甚至不可能构造std::unique_ptr,因为它有抽象方法(即它们只用于描述方法的原型,而不是它的定义/实现)。

new是为数不多的C ++智能指针之一,它在那里我们不必手动处理分配。在代码中使用deleteconst 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" 可能会导致内存泄漏和灾难,因为程序员会分心,因此智能指针会导致此问题不存在。

最后,为了让一个物品回来,你可以简单地将物品向下扔回剑:

item.get()

后者(使用dynamic_cast)将创建一个指向第一个项目的转换指针,来自Sword* method,但形式为Sword。如果Item中的方法或数据成员与auto sword = dynamic_cast<Sword*>(item.get()); if (sword != nullptr) { std::printf("sword power: %d\n", sword->sword_power); } 不相同,则您希望执行此操作。例如,如果您有类似&#34; int sword_power`的内容,那么您可以这样做:

std::variant

当然,检查强制转换是否成功是可选的,但这样做可以防止代码执行未定义的行为(如果强制转换失败并返回空指针)。

使用新的库工具#include <variant> // C++17 struct Sword {}; struct Bottle {}; std::variant<Sword, Bottle> item = Sword{}; 还有另一种方法可以使用这个系统(不是在C ++ 17之前)。

基本上,变体允许您一次拥有多种不同类型中的一种。与元组不同,它允许您有许多不同的类型(如结构),变体一次只允许一个类型中的一个值。为了更好地理解它,以下是它的工作原理:

std::tuple

Sword类似,变体会在模板参数中将其可能的类型作为参数(即Bottleitem类型属于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"; } }; &#39 ;整个类型)。这样,您可以同时拥有一把剑或一瓶,但同时不能同时拥有剑。让我们使用该新功能实现我们的库存。首先,我们必须改变我们的类:

std::variant

我们不再需要虚拟方法和动态多态,您还可以看到我们不再需要动态分配,因为需要Item才能在堆栈中工作(这意味着程序也会更快(也许))。

现在,对于using Item = std::variant<Sword, Bottle>; 概念,我们使用类创建变体的别名:

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

我们也可以将它与矢量一起使用:

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); }

Sword

它检查变体的对象是否保持给定类型的值。在这种情况下,我们检查了std::get<>。然后,如果那里有一把剑,我们使用Sword获取值,该值将operator()作为项目的引用返回。

获取真实对象的另一种方法是使用std::visit。简单地说:访问者的行为就像一个具有重载功能的对象。您可以像访问函数一样调用访问者。为了创建访问者,我们可以使用带有重载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. s或lambdas的结构。这是第一种方法:

std::visit

在这里,operator()将为变体内的当前对象(即项目)调用正确的operator() (Sword&)。如果item持有Sword,则会调用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>...>;

另一种方法是制作重载的lambda。它有点复杂,因为我们没有这方面的库工具,但使用C ++ 17实际上更容易实现它:

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);

然后像这样使用它:

overload

如果您想了解operator()结构中发生了什么,它会继承您提供的所有lambdas,并将using overload重载带入重载查找(因为基类的函数重载不被视为候选者,因此您必须overload)。 {{1}}结构之后的行是user-defined deduction guide,这意味着您可以根据构造函数更改模板结构的模板参数。

答案 1 :(得分:0)

您似乎意外地为两个非常不同的类分配了相同的名称。

一个班级是&#34;项目&#34; - 和&#34; Sword&#34;扩展它。

class Sword: public Item {...};

另一个班级是&#34;库存&#34; - 它代表一个项目列表。

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

然后,您应该确保每个商品只有一个库存,而不是一个库存。然后,将内容添加到此库存应该很容易。

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

注意:您应该避免使用newdelete。尽可能使用标准容器(std::vector)。此外,尽可能使用智能指针(std::unique_ptr)。而不是指向指针,使用智能指针列表:

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

这是一个编码练习建议。它不会影响代码的真正作用,它只会减少混淆(例如,放置delete的位置,对应new)。