我有一个对象指针向量,我正在添加和删除,同时循环更新对象。我似乎无法删除从矢量中“死”的对象而不会导致内存错误。我不确定我做错了什么。下面列出的是我的更新方法,它是子方法。
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;}
}
}
答案 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
并传递您需要传递的任何参数。