delete and erasing an iter? - c++

i'm initializing and inserting into a list like so
_ARRAY_DETAIL* pAR = new _ARRAY_DETAIL;
pAR->sVar1 = 1;
pAR->nVar2 = 2;
m_SomeList.push_back(pAR);
i am trying to find and erase all from the list that contains the value 1, and then delete the pointer we created with new, is my example below doing both in a good, correct efficient way?
while(Iter != m_SomeList.end());
{
if((*Iter)->sVar1 == 1)
{
_ARRAY_DETAIL* pAR = *Iter;
Iter = m_SomeList.erase(Iter);
delete pAR; pAR = NULL;
}
Iter++;
}

Once you erase the iterator, it's no longer valid. You need to increment it before the erase.
if((*Iter)->sVar1 == 1)
{
_ARRAY_DETAIL* pAR = *Iter;
m_SomeList.erase(Iter++);
delete pAR;
}
else
++Iter;
You were correct that erase returns an incremented iterator but I prefer to do it explicitly, before the iterator is erased.
Setting pAR to NULL is redundant, since it's going out of scope on the next line anyway.
Also note that you should only increment Iter if you didn't increment it in the other part of the if.

as an alternative you could use remove if although what you have done seems fine.
bool IsOne (_ARRAY_DETAIL* pAR) {
if(pAR->sVar1 == 1) {
delete pAR;
return true;
}
return false;
}
remove_if (vec.begin(), vec.end(), IsOne);

Related

Removing from std::list while iterating

I have the following code:
bool resetTypeBit = true;
for (auto it = eventsList.begin(); it != eventsList.end(); ++it) {
CreatureEvent* curEvent = *it;
if (curEvent == event) {
it = eventsList.erase(it);
} else if (curEvent->getEventType() == type) {
resetTypeBit = false;
}
}
So I have the following scenario: eventList contains 01 item, and then, as soon as the for statement goes through for the first time and meet the it = eventsList.erase(it); line, the it variable becomes invalid, causing a segmentation fault on the next iteration of the for statement.
Any clues of what could be causing the problem?
If the item you remove is the last item in the list, the erase method will return end(). Your for loop will then try to increment that iterator, which results in undefined behaviour.
Another problem which you haven't come across yet is that, if the item you remove isn't the last item in the list, you'll end up skipping over the following item (because the iterator is incremented past the one that erase returns). You can think of erase as an increment operation that just happens to erase the item first.
The solution is to refactor the loop slightly, to move the increment to the end (and only if erase wasn't called):
bool resetTypeBit = true;
for (auto it = eventsList.begin(); it != eventsList.end(); ) {
CreatureEvent* curEvent = *it;
if (curEvent == event) {
it = eventsList.erase(it);
}
else {
if (curEvent->getEventType() == type) {
resetTypeBit = false;
}
++it; // move the increment to here
}
}
As it is written now, you are incrementing the iterator even in the erase branch, which means that you are always skipping the element just after an erased one. This is both incorrect and results in serious problems if the last element happens to be one to delete. To fix, you have to not increment if you already fix it by setting it to the element following the deleted one.
bool resetTypeBit = true;
for (auto it = eventsList.begin(); it != eventsList.end(); ) {
CreatureEvent* curEvent = *it;
if (curEvent == event) {
it = eventsList.erase(it);
continue;
} else if (curEvent->getEventType() == type) {
resetTypeBit = false;
}
++it;
}

Vector iterator invalidation

I am having an issue and I think it is because of the iterators being invalidated. However I use the iterator from erase() to resume iterating other the structure. When erase() when I try to increment after erase() is called the first time I get the following error
'vector iterator not incrementable '
std::map<uint32_t, std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>>::iterator itMap;
std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>::iterator itVector;
{
tvstd::lock_guard_mutex l(m_ConnectionsMutex);
itMap = m_Connections.find(static_cast<uint32_t>(pcp->ProcessID()));
if (itMap != m_Connections.end())
{
for (itVector = itMap->second.begin(); itVector != itMap->second.end(); ++itVector)
{
if (commadUUID == itVector->first)
{
itVector->second.reset();
itVector = m_Connections[static_cast<uint32_t>(pcp->ProcessID())].erase(itVector);
}
}
}
}
Can anyone see where I am going wrong?
erase returns an iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.
so if you erase you do not need to increment your iterator
for (itVector = itMap->second.begin(); itVector != itMap->second.end(); )
{
if (commadUUID == itVector->first)
{
itVector->second.reset();
itVector = m_Connections[static_cast<uint32_t>(pcp->ProcessID())].erase(itVector);
}
else
{
++itVector
}
}
This solved my issue, I just have to call break after i erase but once i erase i do not need to loop to the end of the list. (#Aleexander solution also works)
std::map<uint32_t, std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>>::iterator itMap;
std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>::iterator itVector;
{
tvstd::lock_guard_mutex l(m_ConnectionsMutex);
itMap = m_Connections.find(static_cast<uint32_t>(pcp->ProcessID()));
if (itMap != m_Connections.end())
{
for (itVector = itMap->second.begin(); itVector != itMap->second.end(); ++itVector)
{
if (commadUUID == itVector->first)
{
itVector->second.reset();
itVector = m_Connections[static_cast<uint32_t>(pcp->ProcessID())].erase(itVector);
break;
}
}
}
}

Can I reuse an invalidated iterator?

The following code works (and admittedly is not the most efficient way to go about this routine). My question is this, is it discouraged to reuse the iterator as I have done here? Might it produce strange behavior? If so, why?
std::map<char, int> map;
map['a'] = 10;
map['b'] = 30;
map['c'] = 50;
map['d'] = 70;
std::map<char, int>::iterator iterator = map.begin();
for (; iterator != map.end(); iterator++) {
if (iterator->second == 30 || iterator->second == 50) {
map.erase(iterator);
iterator = map.begin();
}
}
No, there is nothing wrong with re-assigning to the iterator and reusing it, because after the assignment operator is run, the old value is completely overwritten.
iterator = map.begin();
You're not using an invalidated iterator, but your logic is flawed. To fix it, make a small change to your code; only increment the iterator if you haven't erased an element during the current iteration. With your current code, assume that the first two elements in the map meet the erasure criterion. Then the second one will be left unerased because you increment past it on the second iteration through the loop.
for (; iterator != map.end();) {
if (iterator->second == 30 || iterator->second == 50) {
map.erase(iterator);
iterator = map.begin();
} else {
++iterator;
}
}
If your compiler supports C++11 you can do this instead to erase elements from the map
for (; iterator != map.end(); ) {
if (iterator->second == 30 || iterator->second == 50) {
iterator = map.erase(iterator);
} else {
++iterator;
}
}

Having an issue with c++ stl lists

I have the following function.
void BulletFactory::update(Uint32 ticks) {
std::list<Sprite*>::iterator it = activeBullets.begin();
while (it != activeBullets.end()) {
(*it)->update(ticks);
if(!((*it)->inView())) {
activeBullets.remove(*it);
Sprite* temp = *it;
it++;
inactiveBullets.push_back(temp);
} else {
it++;
}
}
}
when the condition !((*it)->inView()) is being true there is a segmentation fault. I am not able to see the problem.
edit: forgot to mention that activeBullets and inactiveBullets are two lists.
activeBullets.remove(*it);
Sprite* temp = *it; //<--- problem
it++; //<-- problem
should be:
Sprite* temp = *it;
it = activeBullets.erase(it); //use erase
//it++; don't increment it
You must not modify the element that the iterator is pointing to, because it invalidates the iterator. See this question for solutions.

Can you remove elements from a std::list while iterating through it?

I've got code that looks like this:
for (std::list<item*>::iterator i=items.begin();i!=items.end();i++)
{
bool isActive = (*i)->update();
//if (!isActive)
// items.remove(*i);
//else
other_code_involving(*i);
}
items.remove_if(CheckItemNotActive);
I'd like remove inactive items immediately after update them, inorder to avoid walking the list again. But if I add the commented-out lines, I get an error when I get to i++: "List iterator not incrementable". I tried some alternates which didn't increment in the for statement, but I couldn't get anything to work.
What's the best way to remove items as you are walking a std::list?
You have to increment the iterator first (with i++) and then remove the previous element (e.g., by using the returned value from i++). You can change the code to a while loop like so:
std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
bool isActive = (*i)->update();
if (!isActive)
{
items.erase(i++); // alternatively, i = items.erase(i);
}
else
{
other_code_involving(*i);
++i;
}
}
You want to do:
i= items.erase(i);
That will correctly update the iterator to point to the location after the iterator you removed.
You need to do the combination of Kristo's answer and MSN's:
// Note: Using the pre-increment operator is preferred for iterators because
// there can be a performance gain.
//
// Note: As long as you are iterating from beginning to end, without inserting
// along the way you can safely save end once; otherwise get it at the
// top of each loop.
std::list< item * >::iterator iter = items.begin();
std::list< item * >::iterator end = items.end();
while (iter != end)
{
item * pItem = *iter;
if (pItem->update() == true)
{
other_code_involving(pItem);
++iter;
}
else
{
// BTW, who is deleting pItem, a.k.a. (*iter)?
iter = items.erase(iter);
}
}
Of course, the most efficient and SuperCool® STL savy thing would be something like this:
// This implementation of update executes other_code_involving(Item *) if
// this instance needs updating.
//
// This method returns true if this still needs future updates.
//
bool Item::update(void)
{
if (m_needsUpdates == true)
{
m_needsUpdates = other_code_involving(this);
}
return (m_needsUpdates);
}
// This call does everything the previous loop did!!! (Including the fact
// that it isn't deleting the items that are erased!)
items.remove_if(std::not1(std::mem_fun(&Item::update)));
I have sumup it, here is the three method with example:
1. using while loop
list<int> lst{4, 1, 2, 3, 5};
auto it = lst.begin();
while (it != lst.end()){
if((*it % 2) == 1){
it = lst.erase(it);// erase and go to next
} else{
++it; // go to next
}
}
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
2. using remove_if member funtion in list:
list<int> lst{4, 1, 2, 3, 5};
lst.remove_if([](int a){return a % 2 == 1;});
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
3. using std::remove_if funtion combining with erase member function:
list<int> lst{4, 1, 2, 3, 5};
lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a){
return a % 2 == 1;
}), lst.end());
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
4. using for loop , should note update the iterator:
list<int> lst{4, 1, 2, 3, 5};
for(auto it = lst.begin(); it != lst.end();++it){
if ((*it % 2) == 1){
it = lst.erase(it); erase and go to next(erase will return the next iterator)
--it; // as it will be add again in for, so we go back one step
}
}
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
Use std::remove_if algorithm.
Edit:
Work with collections should be like:
prepare collection.
process collection.
Life will be easier if you won't mix this steps.
std::remove_if. or list::remove_if ( if you know that you work with list and not with the TCollection )
std::for_each
The alternative for loop version to Kristo's answer.
You lose some efficiency, you go backwards and then forward again when deleting but in exchange for the extra iterator increment you can have the iterator declared in the loop scope and the code looking a bit cleaner. What to choose depends on priorities of the moment.
The answer was totally out of time, I know...
typedef std::list<item*>::iterator item_iterator;
for(item_iterator i = items.begin(); i != items.end(); ++i)
{
bool isActive = (*i)->update();
if (!isActive)
{
items.erase(i--);
}
else
{
other_code_involving(*i);
}
}
Here's an example using a for loop that iterates the list and increments or revalidates the iterator in the event of an item being removed during traversal of the list.
for(auto i = items.begin(); i != items.end();)
{
if(bool isActive = (*i)->update())
{
other_code_involving(*i);
++i;
}
else
{
i = items.erase(i);
}
}
items.remove_if(CheckItemNotActive);
Removal invalidates only the iterators that point to the elements that are removed.
So in this case after removing *i , i is invalidated and you cannot do increment on it.
What you can do is first save the iterator of element that is to be removed , then increment the iterator and then remove the saved one.
If you think of the std::list like a queue, then you can dequeue and enqueue all the items that you want to keep, but only dequeue (and not enqueue) the item you want to remove. Here's an example where I want to remove 5 from a list containing the numbers 1-10...
std::list<int> myList;
int size = myList.size(); // The size needs to be saved to iterate through the whole thing
for (int i = 0; i < size; ++i)
{
int val = myList.back()
myList.pop_back() // dequeue
if (val != 5)
{
myList.push_front(val) // enqueue if not 5
}
}
myList will now only have numbers 1-4 and 6-10.
Iterating backwards avoids the effect of erasing an element on the remaining elements to be traversed:
typedef list<item*> list_t;
for ( list_t::iterator it = items.end() ; it != items.begin() ; ) {
--it;
bool remove = <determine whether to remove>
if ( remove ) {
items.erase( it );
}
}
PS: see this, e.g., regarding backward iteration.
PS2: I did not thoroughly tested if it handles well erasing elements at the ends.
You can write
std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
bool isActive = (*i)->update();
if (!isActive) {
i = items.erase(i);
} else {
other_code_involving(*i);
i++;
}
}
You can write equivalent code with std::list::remove_if, which is less verbose and more explicit
items.remove_if([] (item*i) {
bool isActive = (*i)->update();
if (!isActive)
return true;
other_code_involving(*i);
return false;
});
The std::vector::erase std::remove_if idiom should be used when items is a vector instead of a list to keep compexity at O(n) - or in case you write generic code and items might be a container with no effective way to erase single items (like a vector)
items.erase(std::remove_if(begin(items), end(items), [] (item*i) {
bool isActive = (*i)->update();
if (!isActive)
return true;
other_code_involving(*i);
return false;
}));
do while loop, it's flexable and fast and easy to read and write.
auto textRegion = m_pdfTextRegions.begin();
while(textRegion != m_pdfTextRegions.end())
{
if ((*textRegion)->glyphs.empty())
{
m_pdfTextRegions.erase(textRegion);
textRegion = m_pdfTextRegions.begin();
}
else
textRegion++;
}
I'd like to share my method. This method also allows the insertion of the element to the back of the list during iteration
#include <iostream>
#include <list>
int main(int argc, char **argv) {
std::list<int> d;
for (int i = 0; i < 12; ++i) {
d.push_back(i);
}
auto it = d.begin();
int nelem = d.size(); // number of current elements
for (int ielem = 0; ielem < nelem; ++ielem) {
auto &i = *it;
if (i % 2 == 0) {
it = d.erase(it);
} else {
if (i % 3 == 0) {
d.push_back(3*i);
}
++it;
}
}
for (auto i : d) {
std::cout << i << ", ";
}
std::cout << std::endl;
// result should be: 1, 3, 5, 7, 9, 11, 9, 27,
return 0;
}
I think you have a bug there, I code this way:
for (std::list<CAudioChannel *>::iterator itAudioChannel = audioChannels.begin();
itAudioChannel != audioChannels.end(); )
{
CAudioChannel *audioChannel = *itAudioChannel;
std::list<CAudioChannel *>::iterator itCurrentAudioChannel = itAudioChannel;
itAudioChannel++;
if (audioChannel->destroyMe)
{
audioChannels.erase(itCurrentAudioChannel);
delete audioChannel;
continue;
}
audioChannel->Mix(outBuffer, numSamples);
}