在以下情况下手动调用析构函数是否是一个糟糕的设计决策?

时间:2016-09-07 18:54:35

标签: c++ oop c++11 destructor

所以我目前正在研究我的OOP / C ++技能,偶然发现了以下情况:

我正在制作一个由实体组成的游戏,必须吃才能生存。他们可以吃其他实体或食物。为了实现这种行为,我创建了一个edible接口,它强制每个实现类创建一个getNutritionalInformation()方法来计算一个单位在宴会后的饱和度。

所以整个事情应该像代码一样工作:

std::unique_ptr<Entity> e1(new Entity);
std::unique_ptr<Entity> e2(new Entity);

std::cout << "Pre feasting energy: " << e1->getNutritionalInformation() << std::endl;
e1->eat(*e2);
std::cout << "After feasting energy: " << e1->getNutritionalInformation() << std::endl;

在此操作之后c1的能量应该高于之前,此值在创建实体期间当前是随机分配的。但是为了模拟被吃实体的 death ,我想在它被吃掉时手动杀死它。我通过以下方式实现了这个目标:

void Entity::eat(Edible& subject) {
   this->energy += subject.getNutritionalInformation();
   delete &subject;
}

但最后这对我来说似乎有点脏,我真的想让智能指针以某种方式知道,它当前持有/指向的对象不再有效。有没有更清洁的方法来实现这一目标?我很确定我尝试这样做的方式非常hacky并且不被认为是正确的OOP编程。

提前感谢您的帮助。

5 个答案:

答案 0 :(得分:12)

好吧,e2“吃”e1它实际上取得了它的所有权。具体而言,它必须拥有它,以便销毁它。所以这样做的方法实际上是这样的:

void Entity::eat(std::unique_ptr<Edible> subject) {
    this->energy += subject->getNutritionalInformation();
}

而且......就是这样。主题将在范围结束时自动销毁。要使用此功能,您需要明确调用std::move;这表示您所有权从调用范围转移到e2。

std::unique_ptr<Entity> e1(new Entity);
std::unique_ptr<Entity> e2(new Entity);

std::cout << "Pre feasting energy: " << e1->getNutritionalInformation() << std::endl;
e1->eat(std::move(e2));
std::cout << "After feasting energy: " << e1->getNutritionalInformation( << std::endl;

请注意,在std::move上调用e2后,e2无法再被假定为实际指向实体(因为已转让所有权)。

答案 1 :(得分:1)

您希望e2的所有者在其变量上调用delete以防止出现问题。

您可以通过两种方式实现这一目标:

e1->eat(std::move(e2));
void Entity::eat(std::unique_ptr<Edible> subject) {
    this->energy += subject->getNutritionalInformation();
}

或者:

e1->eat(*e2);
e2.reset(nullptr);

在任何一种情况下,你的调用者(你的第一个例子中的函数)都知道e2被操作破坏了,在我给出的第一个例子中,因为你移出了它,在第二个例子中因为它手动调用reset(它会破坏旧指针)。

答案 2 :(得分:1)

std::unique_ptr超出范围时,会自动调用对象的析构函数。你不必担心它。

手动销毁对象可能会导致问题。在这种情况下,当eat()手动销毁subject对象时,拥有该对象的std::unique_ptr不知道该对象已被销毁,并且当它再次尝试销毁该对象时将崩溃第二次。

答案 3 :(得分:0)

是的,请不要手动执行。 这实际上你做的几件事 not 中的一件事必须要关心。你的程序为你做到了! 规则是:一旦变量离开范围,它就会被抛弃(desconstructor得到调用)。除了动态分配的内存,你需要释放(删除)变量/ s。

此外,如果您手动删除变量,程序将崩溃:

{
    Type variable;
    delete variable;
} //Scope end: deconstructor get's called. ERROR: already deleated, undefined behaviour, crash. everything destroyed.

答案 4 :(得分:0)

另一种方法是让一些更高级别的经理控制实体的生命周期,所以吃饭时的e2只会做:e1-&gt; kill(); E1-&GT; eatenEnergy(能量); (降低它的营养价值,如果可能部分吃掉实体)。

等到实体经理轮到他们,循环通过所有实体(或对报告的信号作出反应,比如来自e1-&gt; kill()实施中的死亡事件)并释放那些死亡并完全被吃掉/腐烂的人/ etc(杀死那些饥饿的人......)。

如果您希望保持简单的基于事件的世界(如您当前的示例),或者您想要一些&#34;生命周期&#34;发生在世界上,而[内存/实例]管理集中在单一类中。

我认为集中管理器会使事情变得复杂一些,但是随后可能会更容易实现一些可能性,比如更好地调试初始/结束状态,优先处理某些实体处理,重用实体内存(没有动态)分配)等。

在这两种情况下你都应该事先解决一些角落情况(主要是竞争条件)如何解决,例如:

  • e1想要同时吃e2,因为e2想吃e1
  • e1和e3都想同时吃e2
  • e3在被e1吃掉之后尝试吃e2(e2是&#34;死了&#34;但已分配)

因此,实体的状态应设计为在这种情况下有效,并以期望的结果结束。