I am trying to delete an element from a list of objects if one of the object's properties matches a condition. This is my function to do so, however, after performing this operation and then printing the contents, the erase() seems to have no effect. What am I doing wrong here?
void FileReader::DeleteProcess(int id, list<Process> listToDeleteFrom)
{
list<Process>::iterator process;
for(process = listToDeleteFrom.begin(); process != listToDeleteFrom.end(); process++)
{
if (process -> ID == id)
{
listToDeleteFrom.erase(process);
}
}
}
First, you need to pass the list by reference; your code is working on a copy, so changes it makes won't affect the caller's list:
void FileReader::DeleteProcess(int id, list<Process> & listToDeleteFrom)
^
Second, erasing a list element invalidates any iterator that refers to that element, so attempting to carry on iterating afterwards will cause undefined behaviour. If there will only be one element to remove, then return from the function straight after the call to erase; otherwise, the loop needs to be structured something like:
for (auto it = list.begin(); it != list.end(); /* don't increment here */) {
if (it->ID == id) {
it = list.erase(it);
} else {
++it;
}
}
Calling erase() when an iterator is iterating over the list invalidates the iterator. Add the elements to erase to a second list then remove them afterwards.
Also note that you are passing the list by value rather than using a reference or a pointer. Did you mean to use list<Process>& listToDeleteFrom or list<Process>* listToDeleteFrom?
The reason you see no changes reflected is that your list is not being passed by reference, so you are only removing elements from a copy of the list.
Change it to this:
void FileReader::DeleteProcess(int id, list<Process> &listToDeleteFrom) //note &
This will keep the same syntax in the function and modify the original.
However, the way you're deleting the elements is a bit sub-optimal. If you have C++11, the following will remove your invalidation problem, and is more idiomatic, using an existing algorithm designed for the job:
listToDeleteFrom.erase ( //erase matching elements returned from remove_if
std::remove_if(
std::begin(listToDeleteFrom),
std::end(listToDeleteFrom),
[](const Process &p) { //lambda that matches based on id
return p->ID == id;
}
),
std::end(listToDeleteFrom) //to the end of the list
);
Note the keeping of std::list<>::erase in there to actually erase the elements that match. This is known as the erase-remove idiom.
Related
I want to delete vector entries that meet some condition, after calling a function on them. I don't care about stable ordering, so I'd normally, in effect, move the final array element to replace the one I'm examining.
Question: what is the slickest idiom for doing this with iterators?
(Yes, erase-remove is the standard idiom if you want to preserve ordering, but in my case it's not needed and I think will be slower, thanks to those moves, than my version given here.)
With an int subscript I'd do it thusly, and this code works:
for ( int i = (int) apc.size() - 1; i >= 0; i-- )
if ( apc[i]->blah ) {
MyFunc( apc[i] );
apc[i] = apc.back();
apc.pop_back();
}
I try the same with a reverse iterator and it blows up in the ++ of the for loop after it's gone into the if block the first time. I can't figure out why. If were actually calling erase() on *it I know that would render it undefined, but I'm not doing that. I suppose that pop_back() would undefine rbegin(). I should check if it is going into the if block on first iteration and if it only crashes in that situation.
for ( auto it = apc.rbegin(); it != apc.rend(); it++ )
if ( (*it)->blah ) {
MyFunc( *it );
*it = apc.back();
apc.pop_back();
}
With forward iterator it seems to work, though I dislike the stutter effect of having the loop stop when it's finding elements with blah true. A reverse loop is a little ugly, but at least its a real loop, not this centaur-like half-loop-half-while concoction:
for ( auto it = apc.begin(); it != apc.end(); )
if ( (*it)->blah ) {
MyFunc( *it );
*it = apc.back();
apc.pop_back();
} else
it++;
pop_back should normally only invalidate back() and end(). But you can be caught by a corner case if the last element of the array has to be deleted. With indices, no problem, you try to move an element on itself which should be a no-op and proceed on previous index. But with iterators, the current value is back() so it should be invalidated.
Beware, it may also be a problem in your implementation so it could make sense to provide that information so that others could try to reproduce with this or other implementations.
I think the old and tested erase-remove idiom covers this nicely.
apc.erase(std::remove_if(apc.begin(), apc.end(), [](auto& v) {
if (v->blah) {
MyFunc(v);
return true;
}
return false;
}), apc.end());
This idiom moves all the elements to be removed to the end of the container by std::remove_if, and then we remove all of them in one go with erase.
Edit: As pointed out by Marshall, the algorithm will move the elements to be kept to the front, which makes sense considering that it promises to preserve the relative ordering of the kept elements.
If the lambda needs to act on this or any variable other then the passed in v it needs to be captured. In this case we don't need to worry about lifetime, so a default capture by reference is a good choice.
[&](auto& v) {
if (v->blah < x) { //captures x by reference
MyFunc(v, member_variable); //current object is captured by reference, and can access member variables
return true;
}
return false;
})
If MyFunc potentially could modify member_variable we additionally need to make the lambda mutable.
By default the lambda creates a function object with a operator() const but mutable removes the const.
[&](auto& v) mutable { ... }
According to valgrind, this problem is caused by the codes below.I want to remove the element in the list, which contains the same integer value as ref.
while(itr1!=list1.end())
{
if(itr1->num==ref)
{
list1.erase(itr1);
}
else
{itr1++;}
}
list1 is an STL list, the type of list element is NODE, which is a structrue. num is one of the integer element in NODE . itr1 is an iterator of list1. ref is an integer value.
But after I replace with the codes below, it's correct
for(;itr1!=list1.end();itr1++)
{
if(itr1->num==ref)
{
list1.erase(itr1);
itr1--;
}
}
I really couldn't see the difference between the two snippets.
I don't know whether you can figure out the problem with incomplete codes. If you need, I can post all the program. Thanks!
After an erase the iterator of the removed element is invalidated. The second code works because of luck, though this is undefined behaviour and the code should be considered buggy.
The problem is that you are not exiting the loop after erasing the element. Iterators pointing to an erased element are invalidated.
while(itr1!=list1.end())
{
if(itr1->num==ref)
{
list1.erase(itr1);
break;
}
else
{itr1++;}
}
Have you considered using remove_if?
It might be less efficient if you know that you have only one element with that value in the list (remove_if search all occurrencies).
Iterators are really pointers in disguise. When you erase through an iterator, that iterator becomes invalid; it is analogous to doing a delete or free(). The solution is the same as in classic C code:
// Classic C code for removing all nodes matching
// key_to_delete from doubly-linked list:
while (itr != list->null_node)
{
node *next = itr->next; // Calculate next node now, while itr is valid!
if (itr->data == key_to_delete) {
itr->prev->next = itr->next;
itr->next->prev = itr->prev;
list_node_free(itr);
}
itr = next; // Advance to previously calculated next node
}
The same concept in C++ with list container:
while (itr1 != list1.end())
{
std::list<whatever>::iterator next = itr1; // calculate next iterator now
next++; // while itr1 is valid
if (itr1->num == ref)
{
list1.erase(itr1);
}
itr1 = next;
}
The erase operation on a list iterator only invalidates that iterator (and all copies of it). It does not invalidate iterators pointing to other parts of the list. This is why the value of next survives the erasure and can be used.
In C++, how can I delete an element from a vector?
Delete it right from where it is, i.e. let the vector resize
Swap the element to be deleted with the last element s.t. pop_back() can be used (which I hope doesn't involve copying everything around...)
For (1), I've tried the following, but I'm not quite sure if it does what it is supposed to do (remove the item passed to removeItem() ), and it doesn't seem very elegant:
vector<Item*> items;
// fill vector with lots of pointers to item objects (...)
void removeItem(Item * item) {
// release item from memory
if (int i = getItemIdIfExists(item) != -1) {
items.erase (items.begin()+i);
}
}
int getItemIdIfExists(Item * item) {
// Get id of passed-in Item in collection
for (unsigned int i=0; i<items.size(); i++) {
// if match found
if (items[i] == item) return i;
}
// if no match found
return -1;
}
The standard remove+erase idiom removes elements by value:
#include <vector>
#include <algorithm>
std::vector<int> v;
v.erase(std::remove(v.begin(), v.end(), 12), v.end());
remove reorders the elements so that all the erasees are at the end and returns an iterator to the beginning of the erasee range, and erase actually removes the elements from the container.
This is as efficient as you can get with a contiguous-storage container like vector, especially if you have multiple elements of the same value that all get erased in one wash.
void removeItem(Item*item){
for(int i=0; i<items.size(); i++){
if (items[i]==item){
swap(items[i], items.back());
items.pop_back();
return;
}
}
}
Though, if the order doesn't matter, why not just use a std::set?
Delete it right from where it is, i.e. let the vector resize
That's what erase does.
Swap the element to be deleted with the last element s.t. pop_back() can be used (which I hope doesn't involve copying everything around...)
That's what remove does, except that it preserves the order of the remaining objects so it does involve copying everything around.
What you've done could be written as:
items.erase(
std::remove(
items.begin(), items.end()
, item
)
, items.end()
);
The difference with your code being that this will actually remove all items valued item, instead of just the first one.
I'm working with iterators on C++ and I'm having some trouble here. It says "Debug Assertion Failed" on expression (this->_Has_container()) on line interIterator++.
Distance list is a vector< vector< DistanceNode > >. What I'm I doing wrong?
vector< vector<DistanceNode> >::iterator externIterator = distanceList.begin();
while (externIterator != distanceList.end()) {
vector<DistanceNode>::iterator interIterator = externIterator->begin();
while (interIterator != externIterator->end()){
if (interIterator->getReference() == tmp){
//remove element pointed by interIterator
externIterator->erase(interIterator);
} // if
interIterator++;
} // while
externIterator++;
} // while
vector's erase() returns a new iterator to the next element. All iterators to the erased element and to elements after it become invalidated. Your loop ignores this, however, and continues to use interIterator.
Your code should look something like this:
if (condition)
interIterator = externIterator->erase(interIterator);
else
++interIterator; // (generally better practice to use pre-increment)
You can't remove elements from a sequence container while iterating over it — at least not the way you are doing it — because calling erase invalidates the iterator. You should assign the return value from erase to the iterator and suppress the increment:
while (interIterator != externIterator->end()){
if (interIterator->getReference() == tmp){
interIterator = externIterator->erase(interIterator);
} else {
++interIterator;
}
}
Also, never use post-increment (i++) when pre-increment (++i) will do.
I'll take the liberty to rewrite the code:
class ByReference: public std::unary_function<bool, DistanceNode>
{
public:
explicit ByReference(const Reference& r): mReference(r) {}
bool operator()(const DistanceNode& node) const
{
return node.getReference() == r;
}
private:
Reference mReference;
};
typedef std::vector< std::vector< DistanceNode > >::iterator iterator_t;
for (iterator_t it = dl.begin(), end = dl.end(); it != end; ++it)
{
it->erase(
std::remove_if(it->begin(), it->end(), ByReference(tmp)),
it->end()
);
}
Why ?
The first loop (externIterator) iterates over a full range of elements without ever modifying the range itself, it's what a for is for, this way you won't forget to increment (admittedly a for_each would be better, but the syntax can be awkward)
The second loop is tricky: simply speaking you're actually cutting the branch you're sitting on when you call erase, which requires jumping around (using the value returned). In this case the operation you want to accomplish (purging the list according to a certain criteria) is exactly what the remove-erase idiom is tailored for.
Note that the code could be tidied up if we had true lambda support at our disposal. In C++0x we would write:
std::for_each(distanceList.begin(), distanceList.end(),
[const& tmp](std::vector<DistanceNode>& vec)
{
vec.erase(
std::remove_if(vec.begin(), vec.end(),
[const& tmp](const DistanceNode& dn) { return dn.getReference() == tmp; }
),
vec.end()
);
}
);
As you can see, we don't see any iterator incrementing / dereferencing taking place any longer, it's all wrapped in dedicated algorithms which ensure that everything is handled appropriately.
I'll grant you the syntax looks strange, but I guess it's because we are not used to it yet.
This question already has answers here:
What happens if you call erase() on a map element while iterating from begin to end?
(3 answers)
Closed 6 years ago.
I have roughly the following code. Could this be made nicer or more efficient? Perhaps using std::remove_if? Can you remove items from the map while traversing it? Can we avoid using the temporary map?
typedef std::map<Action, What> Actions;
static Actions _actions;
bool expired(const Actions::value_type &action)
{
return <something>;
}
void bar(const Actions::value_type &action)
{
// do some stuff
}
void foo()
{
// loop the actions finding expired items
Actions actions;
BOOST_FOREACH(Actions::value_type &action, _actions)
{
if (expired(action))
bar(action);
else
actions[action.first]=action.second;
}
}
actions.swap(_actions);
}
A variation of Mark Ransom algorithm but without the need for a temporary.
for(Actions::iterator it = _actions.begin();it != _actions.end();)
{
if (expired(*it))
{
bar(*it);
_actions.erase(it++); // Note the post increment here.
// This increments 'it' and returns a copy of
// the original 'it' to be used by erase()
}
else
{
++it; // Use Pre-Increment here as it is more effecient
// Because no copy of it is required.
}
}
You could use erase(), but I don't know how BOOST_FOREACH will handle the invalidated iterator. The documentation for map::erase states that only the erased iterator will be invalidated, the others should be OK. Here's how I would restructure the inner loop:
Actions::iterator it = _actions.begin();
while (it != _actions.end())
{
if (expired(*it))
{
bar(*it);
Actions::iterator toerase = it;
++it;
_actions.erase(toerase);
}
else
++it;
}
Something that no one ever seems to know is that erase returns a new, guaranteed-to-be-valid iterator, when used on any container.
Actions::iterator it = _actions.begin();
while (it != _actions.end())
{
if (expired(*it))
{
bar(*it);
it = _actions::erase(it);
}
else
++it;
}
Storing actions.end() is probably not a good plan in this case since iterator stability is not guaranteed, I believe.
If the idea is to remove expired items, why not use map::erase? This way you only have to remove elements you don't need anymore, not rebuild an entire copy with all the elements that you want to keep.
The way you would do this is to save off the iterators pointing to the elements you want to erase, then erase them all after the iteration is over.
Or, you can save off the element that you visited, move to the next element, and then erase the temporary. The loop bounds get messed up in your case though, so you have to fine tune the iteration yourself.
Depending on how expired() is implemented, there may be other better ways. For example if you are keeping track of a timestamp as the key to the map (as expired() implies?), you can do upper_bound on the current timestamp, and all elements in the range [ begin(), upper_bound() ) need to be processed and erased.