2017-04-12 46 views
3

我有一个库存类,我想在这个类中使用剑,盾和药水类的对象。继承类的数组

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

护盾和药水类与剑类相似。如果我在执行中提出以下几个对象:

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

我该如何将此剑添加到此特定库存?我不认为剑 - >加();将工作,因为剑不知道它是从库存继承。它是否正确? 我试图让add()方法变成虚拟的,因为它必须适用于剑,盾牌和魔药对象。

+0

为什么这些项目从'库存'继承? – user463035818

+0

这个设计似乎有一个基本问题。将商品添加到库存的唯一机制是'add()',它只能将商品添加到自身。作为库存的剑的概念很奇怪。 –

+0

你的班级结构令人困惑 - “Sword' *是一个*库存”是怎么回事?为什么'Inventory'包含其他'Inventory'? – Quentin

回答

0

看来,你不小心将相同的名称分配给两个非常不同的类。

一个类是“Item” - 而“Sword”扩展它。

class Sword: public Item {...}; 

另一类是“库存” - 它代表项目列表。

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

+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抽象一个,所以只要你从Sword的实例调用.description,就会返回"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>?仅仅因为不可能从Sword构建Item。实际上,甚至不可能构建一个Item,因为它具有抽象方法(即它们只是用来描述方法的原型,而不是它的定义/实现)。

std::unique_ptr是少数C++智能指针之一,它在那里,所以我们不必手动处理分配。在代码中使用newdelete可能会导致内存泄漏和灾难,因为程序员的注意力分散,所以智能指针会导致此问题不存在。

最后,为了有一个项目回来,你可以简单地向下转换的东西回剑:

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" 

后者(使用的dynamic_cast)将创建一个转变指针指向第一个项目,从item.get()method,但是以Sword*的形式。如果Sword中存在Item不常见的方法或数据成员,则需要执行此操作。例如,如果你有这样的事情“诠释sword_power`,你可以这样做:

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

当然,如果检查中投成功,是可选的,但这样做可以防止你的代码执行未定义行为(以情况下,转换失败并返回一个空指针)。

目前仍然在做这个系统(之前,C++ 17),使用新的库工具std::variant的另一种方式。

基本上,变种可以让你一次有许多不同的类型之一帽子可以让你有很多不同的类型(比如一个结构体),一个变体只允许一次从一个类型获取一个值。为了更好地理解它,这里是它的工作原理是:

#include <variant> // C++17 

struct Sword {}; 
struct Bottle {}; 

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

std::tuple,一个变异体将具有其可能的类型时,模板参数作为参数(即SwordBottle类型item的整体型的一部分) 。这样,你可以一次有一把剑或一瓶,但永远不会在同一时间。让我们用这个新功能来实现我们的库存。首先,我们必须改变我们的班了一下:

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

我们删除的虚拟方法和动态多态的需求,你会看得更远,我们将不再需要动态分配了,因为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

访问实物的另一种方式是使用std::visit。简而言之,访问者就像一个具有重载功能的对象。你可以像一个函数一样调用访问者。为了创建一个访问者,我们可以使用一个结构超载的operator()或lambda表达式。这是第一个方法:

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. 

这里,std::visit将调用正确的operator()的变种内当前对象(即项目)。如果物品持有剑,则将调用operator() (Sword&)

另一种方法是制作超载的lambdas。这是一个有点复杂的是,因为我们没有为图书馆的工具,但与C++ 17它实际上是更容易实现它:

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

,然后用它像这样:

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结构中发生了什么,它将继承您所给出的所有lambda表达式,并将operator()重载带入重载查找中(因为来自基类的函数重载不被视为候选对象,所以您必须using overload)。 overload结构之后的行是user-defined deduction guide,这意味着您可以根据构造函数更改模板结构的模板参数。