类破坏段错误

时间:2014-12-21 02:47:44

标签: c++ class pointers segmentation-fault

这篇文章不重复。首先阅读我的问题。

我在这里肯定遗漏了一些东西。我希望我的Entity课能够在健康数量为零时销毁自己的实例。

    virtual int takeDamage(int damage)  {
    health -= damage;
    if(health <= 0) delete this;
}

上述代码有什么问题?当调用上面的最后一行时,程序崩溃并出现段错误。 到目前为止,我已经尝试过:

  • 制作自定义析构函数
  • 制作析构函数:

    virtual void destroy(Entity * e){   删除e; }

  • 销毁分配的对象

我删除了takeDamage()的最后一行,然后从delete object调用main

if(tris[i]->getHealth() <=0) delete tris[i]; //called from the main function where a temporary collision detection occurs over all the entities in the program

我找到了一些问题,它归结为以下几点。我的程序中所有Entity(s)的实例(一个特定的专业化,即:三角形)存储在vector中。例如,上面的代码使用此向量:vector<Triangle*> tris;Triangle继承Entity)。然后,迭代该向量并执行各种操作,例如碰撞检测,AI等。现在,当删除其中一个对象时,下次我们遍历整个向量时,我们来到具有的对象被删除了。接下来的问题是,如何缩小该向量? (现在这个是一个标记问题的好地方,可能需要一个单独的问题!)

1 个答案:

答案 0 :(得分:1)

根据您的描述,有一种方法可以安全地使用算法函数。您可能正在寻找的算法函数是std::stable_partitionfor_eacherase

假设您有“游戏循环”,并且您正在测试循环期间的碰撞:

#include <algorithm>
#include <vector>
//...
std::vector<Entity*> allEntities;
//...
while (game_is_stlll_going())
{
    auto it = allEntities.begin();
    while (it != allEntitities.end())
    {
        int damage = 0;
        // ... assume that damage has a value now.
        //...
        // call the takeDamage() function
        it->takeDamage(damage);
        ++it;
    }

    // Now partition off the items that have no health
    auto damaged = std::stable_partition(allEntities.begin(),    
           allEntities.end(), [](Entity* e) { return e->health > 0; });

    // call "delete" on each item that has no health
    std::for_each(damaged, allEntities.end(), [] (Entity *e) { delete e; });

    // erase the non-health items from the vector.
    allEntities.erase(damaged, allEntities.end());

    // continue the game...
} 

基本上循环的作用是我们遍历所有实体并为每个实体调用takeDamage。我们在此阶段不会删除任何实体。循环完成后,我们通过使用std::stable_partition算法函数对损坏的项目进行分区来检查哪些项目没有健康状况。

为什么stable_partition而不只是致电std::removestd::remove_if并在删除项目之前,请在已移除的项目上调用delete?原因是使用remove / remove_if,您无法执行多步删除过程(调用delete然后从向量中删除它)。 remove/remove_if算法函数假定已移动到容器末尾的内容(即“已删除”项目)不再需要,因此除了最后调用vector::erase之外,不能用于任何内容。在已删除的项目上调用delete是未定义的行为。

因此,要解决此问题,您需要首先“分区”坏项,为每个项解除内存,然后 删除它们。我们需要使用分区算法,因此我们可以选择std::stable_partitionstd::partition。哪一个?我们选择std::stable_partition。我们选择std::stable_partition超过std::partition的原因是为了保持项目的相对顺序不变。项目的顺序对于您的游戏实施可能很重要。

现在我们调用stable_partition将项目划分为实体向量的两侧 - 分区左侧的好项目,分区右侧的坏项目。 lambda函数用于通过测试health值来决定哪个实体去哪里。在这种情况下,“分区”是由stable_partition返回的迭代器。

鉴于此,我们调用for_each,其中lambda在分区右侧的每个项目上调用delete,然后调用vector::erase()以删除分区右侧的所有内容。然后我们再次循环,重做整个过程直到游戏结束。

您会注意到的另一件事是上面代码的安全性。如果所有条目都具有一定的健康状况,则对stable_partitionfor_eacherase的调用基本上是无操作。因此,没有必要明确检查至少一个没有健康状况的项目(但如果你觉得需要这种微优化,那么没有什么能阻止你这样做。)

另外,请确保您的Entity基类具有virtual destructor,否则您的代码将在删除时显示未定义的行为。

修改:应将takeDamage()函数重写为而非调用delete this

 virtual int takeDamage(int damage)  {
    health -= damage;
    return 0;
}