如何从矢量中删除对象指针而不会导致内存错误?

时间:2014-12-06 05:33:08

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

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

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

3 个答案:

答案 0 :(得分:2)

一些假设/定义:

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

所以p是一个迭代器,*p是一个Object指针,而**p是一个Object。

问题是这个方法:

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

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

幸运的是,STL为此提供了一个非常好的方法。 vector::erase在您删除的那个之后返回下一个有效的迭代器!因此,您可以使用killUpdate方法更新p而不是for-loop语句,例如。

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,但我相信如果你不这样做就可以看到这个 - 只需执行{ {1}}在你没有调用p++的情况下,在for循环体的末尾。

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

辅助方法是使用std::list覆盖指向已删除对象的每个指针,然后使用nullptr在循环结束时一次性删除它们。 E.g:

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; } } nullptr元素。

既然你似乎是初学者,我应该注意到这一点:

objects

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

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

哪个是lambda expression,并且基本上是具有此类型的对象实例的简写:

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

对第二种方法的最后一点警告,我刚刚发现你在class IsNull { public: bool operator() (const Object* o) const { return o == nullptr; } }; 中的objects上有另一个循环。如果您选择第二种方法,请务必更新此循环以检查scrollUpdate中的nullptr并跳过它们。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

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&&;在C ++ 11中,您必须使用std::unique_ptr<Object>&。必填包括<algorithm><memory>

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