Wanted to know what is the best way to delete an element from a list which is being iterating continuously in an event loop.
I have an event loop where my list is getting updated
for (auto pIt = this->particles.begin(); pIt != this->particles.end(); pIt++) {
(*pIt)->Update(system, gameTime);
}
At a certain event based time I have to remove a random element from the list.
It is handled in a separate function using:
this->particles.remove_if([particle](Particle *ptc)->bool {
return ptc == particle;
});
This gives a list iteration runtime error because the iterators in the loop become invalid. What is the optimal way to solve such kind of situation?
I am assuming the deleting of the element happens inside the Update() function and the "list" is actually std::list and not a std::vector.
Do not perform pIt++ in the loop header. Instead, do pIt++ BEFORE calling Update(). Call Update() on the original object that is returned:
for (auto pIt = this->particles.begin(); pIt != this->particles.end(); ) {
auto pForCall = *(pIt++);
pForCall->Update(system, gameTime);
}
Another case where this will not work is when the element being delete is not the element we're calling Update on.
The erasing function must have access to the iterator used in the loop (make it a member variable) and if that iterator points to the element being deleted increment the iterator. This way you keep the loop iterator from being invalidated by erasing.
Example:
#include <iostream>
#include <list>
struct Explosion;
struct Particle {
void update(Explosion&);
};
struct Explosion {
std::list<Particle> particles;
std::list<Particle>::iterator particle_iter;
void update() {
for(particle_iter = particles.begin(); particle_iter != particles.end(); ) {
auto to_call = particle_iter++;
to_call->update(*this);
}
}
template<class Condition>
void filter(Condition condition) {
for(auto i = particles.begin(); i != particles.end();) {
auto to_remove = i++;
if(condition(*to_remove)) {
// Remove the element.
if(particle_iter == to_remove)
particle_iter = i;
particles.erase(to_remove);
}
}
}
};
void Particle::update(Explosion& explosion) {
explosion.filter([this](Particle& other) { return this == &other; });
// The above ends up destroying this object.
// Cannot no longer access this and should only return.
}
int main() {
Explosion explosion{{Particle{}, Particle{}}, {}};
std::cout << explosion.particles.size() << '\n';
explosion.update();
std::cout << explosion.particles.size() << '\n';
}
Related
Hi I am a beginner in c++ and I would like to know why this code returns a Debug Assertion Failed error everytime an object is erased from the list.
for (auto it = ProjectileList.end(); it != ProjectileList.begin();) {
--it;
if (it->position_y < 0) {
ProjectileList.erase(it);
}
else {
it->Draw(window.renderer);
it->position_y--;
}
}
You have to assign the new iterator that the function erase() returns to it.
for (auto it = ProjectileList.end(); it != ProjectileList.begin();) {
--it;
if (it->position_y < 0) {
it = ProjectileList.erase(it); // assign the new iterator
}
else {
it->Draw(window.renderer);
it->position_y--;
}
}
Another solution would have been to write two loops, one that erases, and the other that draws. Using the std::remove_if could be used:
#include <algorithm>
//...
// Erase
auto iter = std::remove_if(ProjectileList.begin(), ProjectileList.end(),
[&] (auto& p) { return p.position_y < 0; });
ProjectileList.erase(iter, ProjectileList.end());
// Now draw the remaining ones.
for (auto& p : ProjectileList)
{
p.Draw(window.renderer);
p.position_y--;
}
Say I have
vector<shared_ptr<string>> enemy;
how do I remove elements from the enemy vector?
Thanks for your help in advance
**Edit (code in context)
void RemoveEnemy( vector<shared_ptr<Enemy>> & chart, string id )
{
int i = 0;
bool found = FALSE;
for(auto it = chart.begin(); it != chart.end(); i++)
{
if(id == chart[i]->GetEnemyID() )
{
found = TRUE;
chart.erase(it);
}
}
the code above segfaults me
You remove elements the same way you remove any elements from any std::vector - via the std::vector::erase() method, for instance. All you need for that is an iterator to the desired element to remove.
In your case, since you are storing std::shared_ptr<std::string> objects rather than storing actual std::string objects, you may need to use something like std::find_if() to find the vector element containing the desired string value, eg:
void removeEnemy(string name)
{
auto iter = std::find_if(enemy.begin(), enemy.end(),
[&](auto &s){ return (*s == name); }
);
if (iter != enemy.end())
enemy.erase(iter);
}
UPDATE: in the new code you have added, you are incorrectly mixing indexes and iterators together. You are creating an infinite loop if the vector is not empty, as you never increment the it iterator that controls your loop, you are incrementing your index i variable instead (see what happens when you don't give your variables unique and meaningful names?). So you end up going out of bounds of the vector into surrounding memory. That is why you get the segfault error.
Even though you are (trying to) use an iterator to loop through the vector, you are using indexes to access the elements, instead of dereferencing the iterator to access the elements. You don't need to use indexes at all in this situation, the iterator alone will suffice.
Try this instead:
void RemoveEnemy( vector<shared_ptr<Enemy>> & chart, string id )
{
for(auto it = chart.begin(); it != chart.end(); ++it)
{
if (id == it->GetEnemyID() )
{
chart.erase(it);
return;
}
}
Or, using the kind of code I suggested earlier:
void RemoveEnemy( vector<shared_ptr<Enemy>> & chart, string id )
{
auto iter = std::find_if(chart.begin(), chart.end(),
[&](auto &enemy){ return (enemy->GetEnemyID() == id); }
);
if (iter != chart.end())
chart.erase(iter);
}
The problem with your code is that erase() invalidates the iterator. You must use it = chart.erase(it).
I like mine which will remove aliens at high speed and without any care for the ordering of the other items. Removal with prejudice!
Note: remove_if is most often used with erase and it will preserve the order of the remaining elements. However, partition does not care about the ordering of elements and is much faster.
partition-test.cpp:
make partition-test && echo 1 alien 9 alien 2 8 alien 4 7 alien 5 3 | ./partition-test
#include <algorithm>
#include <iostream>
#include <iterator>
#include <memory>
#include <string>
#include <vector>
using namespace std;
template <typename T>
ostream &operator<<(ostream &os, const vector<T> &container) {
bool comma = false;
for (const auto &x : container) {
if (comma)
os << ", ";
os << *x;
comma = true;
}
return os;
}
int main() {
vector<shared_ptr<string>> iv;
auto x = make_shared<string>();
while (cin >> *x) {
iv.push_back(x);
x = make_shared<string>();
}
cout << iv << '\n';
iv.erase(partition(begin(iv), end(iv),
[](const auto &x) { return *x != "alien"s; }),
end(iv));
cout << iv << '\n';
return 0;
}
I am new in programming c++, so please don't be angry with me if my source code is not exactly brilliant.
I have to write a programm to handle with nodes and edges in a graph for my studies.
I have 2 std::lists in my source code. The first one is to store general Nodes and the other one for saving the kind class of my nodes called ArticleNodes. In general, all elements are pointers to the created objects.
To figure out whether one object is the same in the other list I save the memory address and compare it to the elements on the second list. If there is a match the second element will be deleted.
Now I'd like to delete one element in both lists:
void Graph::deleteNode(unsigned int nodeNumber)
{
list<Node*>::iterator it = m_nodes.begin();
ArticleNode* pCurrentArticleNode;
for(unsigned int i=1; i<nodeNumber; i++) { it++; }
Node* pCurrentNode = (*it);
for (list<ArticleNode*>::iterator itArticle = m_articlenode.begin(); itArticle != m_articlenode.end(); itArticle++)
{
pCurrentArticleNode = (*itArticle);
if(pCurrentNode==pCurrentArticleNode) { m_articlenode.remove(pCurrentArticleNode); }
}
m_nodes.remove(pCurrentNode);
delete pCurrentNode;
delete pCurrentArticleNode;
}
I can compile this, but when I call the function, my programm just exits with return 1.
Actually, I figured out that the remove-command in the if-clause is the problem. Why does that not work??
You should use algorithms more than doing everything manually:
void Graph::deleteNode(unsigned int nodeNumber)
{
assert (nodeNumber < m_nodes.size());
auto it = std::next( m_nodes.begin(), nodeNumber - 1 );
auto itArticle = std::find( m_articlenode.begin(), m_articlenode.end(), *it );
if( itArticle != m_articlenode.end() )
m_articlenode.erase( itArticle );
delete *it;
m_nodes.erase(it);
}
Btw your code deletes the same object twice.
When you remove an element from std::list object with remove( ) method, all iterators pointing to that elements become invalid. In your case, after you remove an element from the list m_articlenode, the iterator object itArticle becomes invalid. And when you increment that iterator, you get an undefined behavior.
Pay attention that the method remove( ) deletes all of the items in the list with given value. So you don't need the for-loop at all. Here is the fixed version of your function:
void Graph::deleteNode(unsigned int nodeNumber)
{
list<Node*>::iterator it = m_nodes.begin();
for(unsigned int i=1; i<nodeNumber; i++) { it++; }
Node* pCurrentNode = (*it);
m_articlenode.remove(pCurrentNode);
m_nodes.remove(pCurrentNode);
delete pCurrentNode;
}
You have to use the erase method of std::list in order to remove an element from your list while iterating over it.
This should do the trick:
void Graph::deleteNode(unsigned int nodeNumber)
{
list<Node*>::iterator it = m_nodes.begin();
ArticleNode* pCurrentArticleNode;
for(unsigned int i=1; i<nodeNumber; i++) { it++; }
Node* pCurrentNode = (*it);
list<ArticleNode*>::iterator itArticle = m_articlenode.begin();
while(itArticle != m_articlenode.end()) {
pCurrentArticleNode = (*itArticle);
if(pCurrentNode==pCurrentArticleNode) {
m_articlenode.erase(itArticle++);
} else {
itArticle++;
}
}
m_nodes.remove(pCurrentNode);
delete pCurrentNode;
delete pCurrentArticleNode;
}
Simple issue in your code is that if you go into the if condition is met and it's body executed, you should come out of the loop. std::remove invalidates the iterator ann you will get issues in next iteration so do:
for (list<ArticleNode*>::iterator itArticle = m_articlenode.begin(); itArticle != m_articlenode.end(); itArticle++)
{
pCurrentArticleNode = (*itArticle);
if(pCurrentNode==pCurrentArticleNode)
{
m_articlenode.remove(pCurrentArticleNode);
break;
}
}
In general, there are other issues in the code. As a first step I would suggest using shared_ptr for managing your Nodes and have list of shared_ptr instead of list of pointers.
I am new in programming c++, so please don't be angry with me if my
source code is not exactly brilliant.
We all start somewhere.
Now I'd like to delete one element in both lists:
OK. Quick question. Why are you deleting article node if it is already deleted (via base Node)? I'm assuming for now node is not duplicated in the list:
My solution below... I've passed the lists as arguments. See comments:
#include <list>
#include <algorithm>
struct Node
{
virtual ~Node(){} //For dyna cast to work...
};
struct ArticleNode : Node
{
};
void deleteNode(std::list<ArticleNode*>& articleList, std::list<Node*>& m_nodes, unsigned int nodeNumber)
{
using namespace std;
if (m_nodes.size() > nodeNumber)
{
auto it = m_nodes.begin();
// Advance advances our iterator by N. No need for your for loop - less risk...
std::advance(it,nodeNumber);
Node* currentNode = *it;
//Casting is bad here, but hey, lets assume if type is wrong, we only erase
// it from Node...(Your call)?
ArticleNode* currentArticleNode = dynamic_cast<ArticleNode*>(currentNode);
if (currentArticleNode)
{
//Use find here.... KISS
auto foundPos = std::find(articleList.begin(), articleList.end(), currentArticleNode);
if (foundPos != articleList.end())
{
//No need to delete currentArticleNode, as we're deleting it already...
articleList.erase(foundPos);
}
//Assuming only one item for now...
}
//Else our node was obviously not the right type, and cannot exist in articleNodes...
m_nodes.erase(it);
delete currentNode;
}
else
{
std::cout << "No such node: " << nodeNumber << std::endl;
}
}
I have two vectors
vector<int> vint;
vector<vector<int>::iterator> viter;
What is the best way to remove all elements in vint whose iterators are present in viter. Currently, I workaround temporarily moving to a list
Edit: (Some more background)
This is my current code. I wish I could avoid moving to list and back to vector
void foo(std::vector<Blah>& bvec)
{
std::list<Blah> blist;
std::move(bvec.begin(), bvec.end(), std::back_inserter(blist));
bvec.clear();
std::vector<std::list<Blah>::iterator> selectedElements;
{
//Critical section which holds a mutex. Should be as fast as possible
for(auto it = blist.begin(), it_end= blist.end(); it != it_end; ++it)
{
if(shouldElementBeRemoved(*it))
selectedElements.push_back(it);
}
}
for(auto& it: selectedElements)
{
if(shouldElementReallyBeRemoved(*it))
blist.erase(it);
}
std::move(blist.begin(), blist.end(), std::back_inserter(bvec));
}
Could be simplified without list if I can remove directly from vector.
void foo(std::vector<Blah>& bvec)
{
std::vector<std::vector<Blah>::iterator> selectedElements;
{
//Critical section which holds a mutex. Should be as fast as possible
for(auto it = bvec.begin(), it_end= bvec.end(); it != it_end; ++it)
{
if(shouldElementBeRemoved(*it))
selectedElements.push_back(it);
}
}
for(auto& it: selectedElements)
{
if(shouldElementReallyBeRemoved(*it))
// bvect.erase(it); //Not safe!
}
}
You should be able to use your second snippet with a small modification - instead of iterating over selectedElements in the forward direction, go backwards. Then the call to bvec.erase will never invalidate any iterators that remain in selectedElements.
void foo(std::vector<Blah>& bvec)
{
std::vector<std::vector<Blah>::iterator> selectedElements;
selectedElements.reserve(bvec.size()); // avoid growing the vector while you hold a mutex lock
{
//Critical section which holds a mutex. Should be as fast as possible
for(auto it = bvec.begin(), it_end= bvec.end(); it != it_end; ++it)
{
if(shouldElementBeRemoved(*it))
selectedElements.push_back(it);
}
}
for(auto first = selectedElements.rbegin(),
last = selectedElements.rend();
first != last;
++first)
{
if(shouldElementReallyBeRemoved(**first))
bvec.erase(*first);
}
}
Live demo
This seemed to do the right thing for me:
#include <algorithm>
for (auto current = vint.end();
current >= vint.begin();) {
current--;
if (any_of(
viter.begin(),
viter.end(),
[current](std::vector<int>::iterator it) {
return current == it;
})) {
vint.erase(current);
}
}
Using any_of allows the iterator vector to be unsorted.
Perhaps this would be appropriate for your situation:
struct Blah
{
bool deleted = false; // add a special field
// other data
};
void foo(std::vector<Blah>& bvec)
{
{
//Critical section which holds a mutex. Should be as fast as possible
// mark them quickly
for(auto& b: bvec)
if(shouldElementBeRemoved(b))
b.deleted = true;
}
// remove them slowly
for(auto i = bvec.begin(); i != bvec.end();)
{
if(i->deleted && shouldElementReallyBeRemoved(*i))
i = bvec.erase(i);
else
{
i->deleted = false;
++i;
}
}
}
I have this loop
for(int i=0;i<vec1.size();++i)
{
if(vec1[i]==*p)
{
vec1[i]=*p;
cout<<"element updated"<<endl;
}
else
{
cout<<"push_back"<<endl;
vec1.push_back(*p);
}
}
I'm inserting objects in container class and I've overloaded the == to check two parameters inside the object and if they match I want to update the them and if they don't match I want to put them in the vector, but I don't seem to be able to properly populate my vector, when I do vec1.size() I get 0 even when I insert 3 objects.
You're problem is that your if is inside your search loop. Your if will never be executed, because your loop body never runs, because your .size() will never be greater than 0.
Try this:
// UNTESTED
std::vector<person> vec1;
add(person *p) {
std::vector<person>::iterator it = std::find(vec1.begin(), vec1.end(), *p);
if(it == vec1.end())
vec1.push_back(*p);
else
*it = *p;
}
Or, if you really want to code the loop by hand:
// UNTESTED
std::vector<person> vec1;
add(person *p) {
int i;
for(i=0;i<vec1.size();++i) {
if(vec1[i] == *p)
break;
}
if(i == vec1.size())
vec1.push_back(*p);
else
vec1[i] = *p;
}
Of course, you might consider changing your container. Using a std::map would shorten your code and reduce the time it takes to manipulate large data sets.
std::map<std::string, person> map1;
add(person *p) {
map1[p->name] = *p;
}
When the vec1 starts from empty, the for loop is not going to run. So you want to have at least one element in vec1 to start with. How about add this:
vec1.push_back(*p);
for(int i=0;i<vec1.size();++i){//the rest}