我有一个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()方法设为虚拟,因为它必须适用于剑,盾和药水对象。
答案 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 ++智能指针之一,它在那里我们不必手动处理分配。在代码中使用delete
和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"
可能会导致内存泄漏和灾难,因为程序员会分心,因此智能指针会导致此问题不存在。
最后,为了让一个物品回来,你可以简单地将物品向下扔回剑:
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
类似,变体会在模板参数中将其可能的类型作为参数(即Bottle
和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"; }
};
&#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);
注意:您应该避免使用new
和delete
。尽可能使用标准容器(std::vector
)。此外,尽可能使用智能指针(std::unique_ptr
)。而不是指向指针,使用智能指针列表:
Item** inventory; // works, but not so good
std::vector<std::unique_ptr<Item>>; // better
这是一个编码练习建议。它不会影响代码的真正作用,它只会减少混淆(例如,放置delete
的位置,对应new
)。