【问题标题】:How do I delete an object pointer from a vector without causing a memory error?如何在不导致内存错误的情况下从向量中删除对象指针?
【发布时间】:2014-12-06 05:33:08
【问题描述】:

我有一个对象指针向量,我在循环更新对象时添加和删除它。我似乎无法从向量中删除已“死亡”的对象而不会导致内存错误。我不确定我做错了什么。下面列出的是我的更新方法和它的子方法。

void Engine::update(string command){
    if(getGameOver()==false){
        for(p=objects.begin();p!=objects.end();p++){
        spawnUpdate(command);
        //cout<<"Spawn"<<endl;
        objectUpdate(command);
        //cout<<"object"<<endl;
        scrollUpdate(command);
    //  cout<<"scroll"<<endl;
        killUpdate(command);
        //cout<<"kill"<<endl;
}
}
}

void Engine::killUpdate(std::string command){
    if((*p)->getIsDead()==true){delete *p;}
}

void Engine::objectUpdate(string command){
    (*p)->update(command,getNumObjects(),getObjects());
    if(((*p)->getType() == PLAYER)&&((*p)->getPosX()>=getFinishLine())){setGameOver(true);}
}

void Engine::scrollUpdate(string command){
            //Check player position relative to finishLine
            if(((*p)->getType() == PLAYER)&&((*p)->getPosX()>(SCREEN_WIDTH/2))){
                (*p)->setPosX((*p)->getPosX()-RUN_SPEED);
                setFinishLine(getFinishLine()-RUN_SPEED);

                for(q=objects.begin();q!=objects.end();q++){
                    //Shift objects to pan the screen
                    if((*q)->getType() == OPPONENT){(*q)->setPosX((*q)->getPosX()-RUN_SPEED);}
                    if((*q)->getType() == BLOCK){(*q)->setPosX((*q)->getPosX()-RUN_SPEED);}
                }
            }
}

void Engine::spawnUpdate(string command){
    if(command.compare("shoot")==0){
        cout<<"Bang!"<<endl;
            if((*p)->getType() == PLAYER){objects.push_back(new Bullet((*p)->getPosX(),(*p)->getPosY(),(*p)->getState()));cout<<"Bullet success "<<endl;}
        }
}

【问题讨论】:

  • 您需要从std::vector 中删除已删除的内存,否则下一轮您将测试未分配的内存。否则,您可以将它们设置为nullptr,并在使用它们之前检查nullptr。 (或者在处理完之后从std::vector 中删除所有nullptr 元素)。
  • 第一件事是命名变量比pq 更具描述性。其次,更好地格式化你的代码,因为代码很难阅读。
  • 问题是你在循环你的向量时改变了你的向量的大小。这将使迭代器无效——不要编写代码来改变向量的大小,同时你在同一个向量上循环——问题不仅仅是delete调用。
  • 我该如何解决这个问题? update 方法在游戏未结束时不断调用,我需要遍历向量才能知道需要删除哪些对象。
  • 管理此问题的一种方法是在处理std::vector 时将新元素添加到单独的std::vector 并使用nullptr 标记要删除的元素。然后在完成整个std::vector 的处理后,通过擦除nullptr 元素并从另一个向量中附加新元素来进行更改。 (或从新的 std::vector 替换 nullprt 元素并附加/删除差异)。

标签: c++ pointers object vector delete-operator


【解决方案1】:

一些假设/定义:

  • objects 成员变量,类似于 vector&lt;Object*&gt; objects;
  • p 也是一个成员变量,类似于 vector&lt;Object*&gt;::iterator p;

所以p 是一个迭代器,*p 是一个对象指针,**p 是一个对象。

问题在于这个方法:

void Engine::killUpdate(std::string command) {
  if ((*p)->getIsDead() == true) {
    delete *p;
  }
}

释放*p指向的Object,即p迭代器所引用位置的向量中的指针。但是指针*p 本身仍在向量中,现在它只是指向不再分配的内存。下次尝试使用此指针时,将导致未定义的行为并很可能崩溃。

因此,一旦删除了它指向的对象,就需要从向量中删除该指针。这可能很简单:

void Engine::killUpdate(std::string command) {
  if ((*p)->getIsDead() == true) {
    delete *p;
    objects.erase(p);
  }
}

但是,您正在循环中从update 调用killUpdate,该循环遍历objects 向量。如果你使用上面的代码,你会遇到另一个问题:一旦你从objects向量中删除p,在你的for循环语句中执行p++就不再安全了,因为p不再是一个有效的迭代器。

幸运的是,STL 提供了一种很好的解决方法。 vector::erase 在您删除的迭代器之后返回下一个有效迭代器!所以你可以让killUpdate 方法更新p 而不是你的for循环语句,例如

void Engine::update(string command) {
  if (getGameOver() == false) {
    for (p = objects.begin(); p != objects.end(); /* NOTHING HERE */) {
      // ...
      killUpdate(command);
    }
  }
}

void Engine::killUpdate(std::string command) {
  if ((*p)->getIsDead() == true) {
    delete *p;
    p = objects.erase(p);
  } else {
    p++;
  }
}

这当然是假设你总是在循环中调用killUpdate,但我相信如果你不这样做,你可以看到解决方法——只需执行p++在你没有调用killUpdate的情况下,在for循环体的末尾。

还请注意,这不是特别有效,因为每次擦除向量的元素时,都必须将其后面的元素移回以填充空白空间。因此,如果您的 objects 向量很大,这会很慢。如果您使用 std::list 代替(或者如果您已经在使用它),那么这不是问题,但列表还有其他缺点。

第二种方法是用nullptr覆盖每个指向已删除对象的指针,然后在循环结束时使用std::remove_if一次性将它们全部删除。例如:

void Engine::update(string command) {
  if (getGameOver() == false) {
    for (p = objects.begin(); p != objects.end(); p++) {
      // ...
      killUpdate(command);
    }
  }
  std::erase(std::remove_if(objects.begin(), objects.end(), 
                            [](const Object* o) { return o == nullptr; }), 
             objects.end());
}

void Engine::killUpdate(std::string command) {
  if ((*p)->getIsDead() == true) {
    delete *p;
    *p = nullptr;
  } 
}

这次的假设是,您永远不会有 objectsnullptr 元素出于某种原因想要保留。

由于您似乎是初学者,我应该注意这一点:

  std::erase(std::remove_if(objects.begin(), objects.end(), 
                            [](const Object* o) { return o == nullptr; }),
             objects.end());

the erase-remove idiom, which is explained well on Wikipedia。如果在调用给定函数对象时返回 true,它将从向量中删除元素。在这种情况下,函数对象是:

[](const Object* o) { return o == nullptr; }

lambda expression 本质上是这种类型的对象实例的简写:

class IsNull {
 public:
   bool operator() (const Object* o) const {
     return o == nullptr;
   }
};

对第二种方法的最后一个警告,我刚刚注意到您在scrollUpdate 中有另一个循环超过objects。如果您选择第二种方法,请务必更新此循环以检查 objects 中的 nullptrs 并跳过它们。

【讨论】:

  • 当尝试实现第二个选项时,它声明 remove_if 不是命名空间 std 的成员。
  • 经过一番搜索,我发现它需要#include 。我认为我仍然有问题,因为我仍然收到内存错误。
  • 没有std::erase - 你的意思是objects.erase
【解决方案2】:

这是一个问题(为便于阅读而格式化):

void Engine::update(string command)
{
    if (getGameOver()==false)
    {
        for (p=objects.begin();p!=objects.end();p++)
        {
            spawnUpdate(command);  // changes vector
//...
       }
    }
//...
}

void Engine::spawnUpdate(string command)
{
//...
    objects.push_back(new Bullet((*p)->getPosX(),(*p)->getPosY(),(*p)->getState())); // no
//...
}

您有一个带有迭代器p 的循环,它指向object 向量中的元素。当您调用objects.push_back 时,向量的迭代器可能会失效。因此,循环迭代器p 不再有任何好处。在for() 中增加它会导致未定义的行为。

解决此问题的一种方法是创建一个临时向量来保存您的更新。然后在处理结束时添加更新:

void Engine::update(string command)
{
    std::vector<Object*> subVector;
    if (getGameOver()==false)
    {
        for (p=objects.begin();p!=objects.end();p++)
        {
            spawnUpdate(command, subVector);
            //...
        }
    }

    // add elements to the vector
    object.insert(object.end(), subVector.begin(), subVector.end());
}

void Engine::spawnUpdate(string command, std::vector<Object*>& subV)
{
    if (command.compare("shoot")==0)
    {
        cout<<"Bang!"<<endl;
        if ((*p)->getType() == PLAYER)
            subV.push_back(new Bullet((*p)->getPosX(),(*p)->getPosY(),(*p)->getState()));
        cout<<"Bullet success "<<endl;
    }
}

【讨论】:

    【解决方案3】:

    您可以通过不使用原始指针来避免大多数这些问题。显然您的代码使用了向量拥有指针的语义,因此您可以直接表达这一点:

    std::vector< std::unique_ptr<Object> > objects;
    

    然后你可以使用 objects.emplace_back(arguments,to,Object,constructor); 插入到向量中,当你从向量中删除时,它会自动 delete 对象。

    您仍然需要注意 erase 使迭代器无效,因此请继续使用 Tyler McHenry 解释的擦除删除习语。例如:

    objects.erase( std::remove_if( begin(objects), end(objects),
        [](auto&& o) { return o->getIsDead(); }),  end(objects) );
    

    注意 - 自 C++14 起,auto&amp;&amp; 在此处被允许;在 C++11 中,您必须使用 std::unique_ptr&lt;Object&gt;&amp;。必需的包括 &lt;algorithm&gt;&lt;memory&gt;

    请停止使用全局迭代器,将 p 保留在函数本地并传递您需要传递的任何参数。

    【讨论】:

      猜你喜欢
      • 2020-07-13
      • 1970-01-01
      • 1970-01-01
      • 2021-07-03
      • 2014-07-28
      • 2014-04-28
      • 1970-01-01
      • 2013-07-05
      • 1970-01-01
      相关资源
      最近更新 更多