如何从矢量中删除对象指针而不导致内存错误
How do I delete an object pointer from a vector without causing a memory error?
>我有一个对象指针向量,我在循环更新对象时正在添加和删除它。我似乎无法在不导致内存错误的情况下从矢量中删除已"死亡"的对象。我真的不确定我做错了什么。下面列出的是我的更新方法,它是子方法。
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;}
}
}
一些假设/定义:
-
objects
成员变量,类似于vector<Object*> objects;
-
p
也是一个成员变量,类似于vector<Object*>::iterator p;
因此,p
是一个迭代器,*p
是一个 Object 指针,**p
是一个对象。
问题是这种方法:
void Engine::killUpdate(std::string command) {
if ((*p)->getIsDead() == true) {
delete *p;
}
}
解除分配由 *p
指向的对象,该指针位于p
迭代器引用的位置处的向量中。然而,指针*p
本身仍在向量中,现在它只指向不再分配的内存。下次尝试使用此指针时,将导致未定义的行为,并且很可能崩溃。
因此,删除指向的对象后,您需要从矢量中删除此指针。这可以像这样简单:
void Engine::killUpdate(std::string command) {
if ((*p)->getIsDead() == true) {
delete *p;
objects.erase(p);
}
}
但是,您将在循环objects
向量中从update
调用killUpdate
。如果使用上面的代码,您将遇到另一个问题:一旦从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
,但我相信如果你不这样做,你可以看到解决这个问题的方法——只要在 for 循环主体的末尾执行 p++
在你没有调用 killUpdate
另请注意,这不是特别有效,因为每次擦除矢量的元素时,其后的元素都必须移回以填充空白空间。因此,如果您的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;
}
}
这次的假设是,由于某种原因,您永远不会有想要保留的objects
nullptr
元素。
由于您似乎是初学者,因此我应该注意这一点:
std::erase(std::remove_if(objects.begin(), objects.end(),
[](const Object* o) { return o == nullptr; }),
objects.end());
是擦除-删除成语,在维基百科上解释得很好。如果元素在调用给定函数对象时返回 true,它会从向量中删除元素。在这种情况下,函数对象为:
[](const Object* o) { return o == nullptr; }
这是一个 lambda 表达式,本质上是具有此类型的对象实例的简写:
class IsNull {
public:
bool operator() (const Object* o) const {
return o == nullptr;
}
};
第二种方法的最后一个警告,我刚刚注意到您在scrollUpdate
中对objects
有另一个循环。如果选择第二种方法,请务必更新此循环以检查objects
中的nullptr
并跳过它们。
这是一个问题(为便于阅读而格式化(:
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;
}
}
您可以通过不使用原始指针来避免这些问题中的大多数。 显然,您的代码使用向量拥有指针的语义,因此您可以直接表达这一点:
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
保留在函数的本地,并传递需要传递的任何参数。
- 多个文件的内存分配错误"在抛出 'std :: bad_alloc' what (): std :: bad_alloc 的实例后终止调用" [C++]
- 是否值得降低我的代码的可读性,以便在出现内存不足错误时提供异常安全性?
- 内存错误低于在C++年实现埃拉托色尼筛分时的预期
- 共享内存:MapViewOfFile 返回错误 5
- 将 vector<vector<int>> 传递到函数中会产生内存错误
- 释放动态内存时C++错误
- 我有一个线程 1:EXC_BAD_ACCESS(代码 = 1,地址 = 0x8)错误.我认为这是由于内存管理不好.我可以
- 复制内存给出分段错误
- C++程序错误:malloc():内存损坏
- 在我的以下代码中获取 MLE(内存限制错误).尝试解决 ROUND C 2019(问题 A-摆动行走)启动问题
- 为什么删除分配的阵列会导致内存错误?
- 需要找到3个小错误-内存错误
- 错误:内存位置的 std::length_error
- 修复了瓦尔格林德错误内存泄漏的错误
- 错误:“内存集”未在此范围内声明
- OpenCV 2.4.5运行时错误内存
- OpenCV错误:内存不足
- STL映射,设置错误:内存超出分配块的末尾
- GLUT:致命错误:内存不足
- MPI错误:内存不足-有哪些解决方案选项