C++ 如何从向量中删除对象指针而不导致内存错误?
我有一个对象指针向量,在循环更新对象的过程中添加和删除这些指针。我似乎无法删除向量中“死亡”的对象,而不会导致内存错误。我真的不确定我做错了什么。下面列出的是我的更新方法及其子方法C++ 如何从向量中删除对象指针而不导致内存错误?,c++,pointers,object,vector,delete-operator,C++,Pointers,Object,Vector,Delete Operator,我有一个对象指针向量,在循环更新对象的过程中添加和删除这些指针。我似乎无法删除向量中“死亡”的对象,而不会导致内存错误。我真的不确定我做错了什么。下面列出的是我的更新方法及其子方法 void Engine::update(string command){ if(getGameOver()==false){ for(p=objects.begin();p!=objects.end();p++){ spawnUpdate(command); /
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;}
}
}
void引擎::更新(字符串命令){
如果(getGameOver()==false){
对于(p=objects.begin();p!=objects.end();p++){
更新(命令);
//coutgetPosX()-运行速度);}
}
}
}
无效引擎::生成更新(字符串命令){
if(command.compare(“shoot”)==0){
coutgetState());cout一些假设/定义:
对象
成员变量,类似于向量对象;
p
也是一个成员变量,类似于vector::iterator p;
因此,p
是一个迭代器,*p
是一个对象指针,**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);
}
}
但是,您在一个循环中从update
调用killUpdate
,该循环迭代对象
向量。如果使用上述代码,您将遇到另一个问题:一旦您从对象
向量中删除p
,则在for循环中执行p++/code>将不再安全声明,因为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
,但我相信如果您不这样做,您可以看到解决方法——在没有调用killUpdate
的情况下,只需在for循环体的末尾执行p++
还请注意,这不是特别有效的,因为每次删除向量的元素时,后面的元素都必须移回以填充空白。因此,如果对象
向量很大,这将很慢。如果改用标准::列表
(或者您已经在使用它),那么这不是问题,但列表还有其他缺点
第二种方法是用nullptr
覆盖每个指向已删除对象的指针,然后在循环结束时使用std::remove_,如果
,则一次删除所有指针。例如:
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
元素
由于您似乎是初学者,我应该注意:
std::erase(std::remove_if(objects.begin(), objects.end(),
[](const Object* o) { return o == nullptr; }),
objects.end());
是。当给定函数对象被调用时,如果返回true,则从向量中删除元素。在这种情况下,函数对象为:
[](const Object* o) { return o == nullptr; }
它是一种类型,本质上是此类型对象实例的简写:
class IsNull {
public:
bool operator() (const Object* o) const {
return o == nullptr;
}
};
第二种方法的最后一个警告是,我刚刚注意到在scrollUpdate
中的对象上有另一个循环。如果选择第二种方法,请确保更新此循环以检查对象中的nullptr
并跳过它们。这里有一个问题(格式化为可读性):
您有一个带有迭代器p
的循环,它指向对象
向量中的元素。当您调用对象时,推回
,向量的迭代器可能会失效。因此循环迭代器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;
}
}
void引擎::更新(字符串命令)
{
std::向量子向量;
如果(getGameOver()==false)
{
对于(p=objects.begin();p!=objects.end();p++)
{
生成更新(命令,子向量);
//...
}
}
//向向量添加元素
insert(object.end(),subVector.begin(),subVector.end());
}
无效引擎::生成更新(字符串命令,std::vector和subV)
{
if(command.compare(“shoot”)==0)
{
coutgetState());
cout您可以通过不使用原始指针来避免这些问题。很明显,您的代码使用了向量拥有指针的语义,因此您可以直接表达:
std::vector< std::unique_ptr<Object> > objects;
注意-自动&
在这里是允许的,因为C++14;在C++11中,您必须使用std::unique\u ptr&
。必需的包括
和
并且请停止使用全局迭代器,使函数保持p
本地,并传递任何需要传递的参数。您需要从std::vector
中删除已删除的参数,否则下次您将测试未分配内存。否则,您可以将它们设置为nullptr
并检查nullptr在使用它们之前进行编码。(或者在处理后从std::vector
中删除所有nullptr
元素)。首先,命名变量要比p
和q
更具描述性。其次,代码的格式要稍微好一点,因为代码是h
std::vector< std::unique_ptr<Object> > objects;
objects.erase( std::remove_if( begin(objects), end(objects),
[](auto&& o) { return o->getIsDead(); }), end(objects) );