I am debugging a big C++98 application and I am obtaining a SIBGUS error when a method tries to increment a std::map::iterator.
By putiing traces, I have discovered that the method in question removes elements from the mentioned map (indireclty, by calling other methods that call other methods and so on...), so I suspect that the problem is to been iterating over the map while deleting its elements.
I have been searching the proper way to iterate over a std::map and delete items safelly, and I have found this:
for (auto it = m.cbegin(); it != m.cend() /* not hoisted */; /* no increment */)
{
if (must_delete)
{
m.erase(it++); // or "it = m.erase(it)" since C++11
}
else
{
++it;
}
}
Code quoted from How to remove from a map while iterating it?
I have some questions about this:
Is it actually necessary to distinguis between deleting elements or not, taking into account that the iterator is going to be increased in any case?
Is the following code snippet equivalent to the above one, in terms of safety?
for (auto it = m.cbegin(); it != m.cend() /* not hoisted */; /* no increment */)
{
if (must_delete)
{
m.erase(it);
}
it++;
}
The method that is producing the SIGBUS follows the following pattern:
std::map<..., ...>::iterator it = myMap.begin(); // myMap is an instance attribute and can be accessed by any class method.
while(it != myMap.end() {
if(somethingHappens())
doSomethingThatMightDeleteMapElements(); // this can (or not) delete 'myMap' elements.
it++; // The error occurs here
}
Since the deletion is performed by other method/s, I cannot distinguis if an element has been deleted or not (unless I return a boolean value or similar). Is this potentially unsafe?
Is the following code snippet equivalent to the above one, in terms of safety?
No, of course not, you cannot increase it after you passed it to map.erase() as that iterator is invalidated by that call. Difference is:
map.erase(it++);
is logically equivalent to:
iterator tmp = it;
++it;
map.erase( tmp );
So in this case tmp is invalidated, but it is still valid.
Considering this code:
while(it != myMap.end() {
if(somethingHappens())
doSomethingThatMightDeleteMapElements(); // this can (or not) delete 'myMap' elements.
it++; // The error occurs here
}
I think only viable way would be this:
while(it != myMap.end() {
if(somethingHappens()) {
key_type key = it->first();
doSomethingThatMightDeleteMapElements(); // this can (or not) delete 'myMap' elements.
it = myMap.upper_bound( key );
} else
it++;
}
Since the deletion is performed by other method/s, I cannot distinguish if an element has been deleted or not (unless I return a boolean value or similar).
You certainly can. Keep the size of the map before the call to doSomethingThatMightDeleteMapElements. Get the the size of the map after the call to doSomethingThatMightDeleteMapElements. Then take appropriate action depending on whether they are equal or not.
while(it != myMap.end() {
size_t size_before = myMap.size();
size_t size_after = size_before;
if(somethingHappens())
{
doSomethingThatMightDeleteMapElements(); // this can (or not) delete myMap elements.
size_after = myMap.size();
}
if ( size_before != size_after )
{
// Be safe. Iterate from the start again.
it = myMap.begin();
}
else
{
it++; // The error occurs here
}
}
Related
In the following code I loop through a map and test if an element needs to be erased. Is it safe to erase the element and keep iterating or do I need to collect the keys in another container and do a second loop to call the erase()?
map<string, SerialdMsg::SerialFunction_t>::iterator pm_it;
for (pm_it = port_map.begin(); pm_it != port_map.end(); pm_it++)
{
if (pm_it->second == delete_this_id) {
port_map.erase(pm_it->first);
}
}
UPDATE: Of course, I then read this question which I didn't think would be related but answers my question.
C++11
This has been fixed in C++11 (or erase has been improved/made consistent across all container types).
The erase method now returns the next iterator.
auto pm_it = port_map.begin();
while(pm_it != port_map.end())
{
if (pm_it->second == delete_this_id)
{
pm_it = port_map.erase(pm_it);
}
else
{
++pm_it;
}
}
C++03
Erasing elements in a map does not invalidate any iterators.
(apart from iterators on the element that was deleted)
Actually inserting or deleting does not invalidate any of the iterators:
Also see this answer:
Mark Ransom Technique
But you do need to update your code:
In your code you increment pm_it after calling erase. At this point it is too late and is already invalidated.
map<string, SerialdMsg::SerialFunction_t>::iterator pm_it = port_map.begin();
while(pm_it != port_map.end())
{
if (pm_it->second == delete_this_id)
{
port_map.erase(pm_it++); // Use iterator.
// Note the post increment.
// Increments the iterator but returns the
// original value for use by erase
}
else
{
++pm_it; // Can use pre-increment in this case
// To make sure you have the efficient version
}
}
Here's how I do that ...
typedef map<string, string> StringsMap;
typedef StringsMap::iterator StrinsMapIterator;
StringsMap m_TheMap; // Your map, fill it up with data
bool IsTheOneToDelete(string str)
{
return true; // Add your deletion criteria logic here
}
void SelectiveDelete()
{
StringsMapIter itBegin = m_TheMap.begin();
StringsMapIter itEnd = m_TheMap.end();
StringsMapIter itTemp;
while (itBegin != itEnd)
{
if (IsTheOneToDelete(itBegin->second)) // Criteria checking here
{
itTemp = itBegin; // Keep a reference to the iter
++itBegin; // Advance in the map
m_TheMap.erase(itTemp); // Erase it !!!
}
else
++itBegin; // Just move on ...
}
}
This is how I would do it, approximately:
bool is_remove( pair<string, SerialdMsg::SerialFunction_t> val )
{
return val.second == delete_this_id;
}
map<string, SerialdMsg::SerialFunction_t>::iterator new_end =
remove_if (port_map.begin( ), port_map.end( ), is_remove );
port_map.erase (new_end, port_map.end( ) );
There is something odd about
val.second == delete_this_id
but I just copied it from your example code.
The following code throwing debug assertion map/iterator incremental error ..
void ClassA::Remove()
{
std::map<int, CVClassB*>::iterator it(m_p.begin());
while ( it != m_p.end() )
{
if (it->first >= 0)
{
m_p.erase(it);
it++;
}
}
}
Can you please let me know what is the error
std::map::erase invalidates the iterator on which it operates. So it is not safe to increment it afterwards. But erase() does return the next iterator for you:
it = m_p.erase(it);
Also, you only increment it inside the if, so unless all the keys are >=0, you will get stuck in an infinite loop. You probably wanted something like:
// delete all keys >= 0
if (it->first>=0) {
it = m_p.erase(it); // erase and increment
}
else {
++it; // just increment
}
Also, as Vlad's answer alludes to, who manages the lifetime of the CVClassB*? Do you need to delete it? Why use a pointer at all, you can probably store the value in the map directly. (Or use a smart pointer).
Write the loop like
while ( it != m_p.end() )
{
if (it->first >= 0)
{
it = m_p.erase(it);
}
else
{
++it;
}
}
Also it seems you should delete the object pointed to by the erased iterator.
For example
delete *it;
it = m_p.erase(it);
Your invalidating the iterator by removing inside the loop but in any case all that does is clear the map. Just call m_p.clear() and it will do exactly what you are trying to do. Although not sure what your trying to do is what you intended to do but that's another issue.
If you want to delete the objects pointed to then delete them then clear the map.
for(item : m_p)
delete item->second;
m_p.clear();
//done
I have a problem and dont know how to proper solve it or WHY the error appear.
To my problem:
I have 1 loop which execute a function every 2 seconds. That functions does a for() function and erase all entrys which remaining time is at 0. If remaining time is not 0 then it will decrease it by 2000 (2sec).
But after erasing an entry the program crashes...
boost map:
boost::unordered_map<unsigned int, sBUFF_INFO*> p_BuffInfo;
function which get executed from 2 seconds loop
void CSkill::DecreaseAllBuffRemTime()
{
for( itertype(p_BuffInfo) it = p_BuffInfo.begin(); it != p_BuffInfo.end(); it++ )
{
sBUFF_INFO* buff = it->second;
if(buff != NULL)
{
if(buff->dwTimeRemaining <= 0)
{
this->DelPcBuffInfo(buff->tblidx)
}else{
buff->dwTimeRemaining -= 2000;
}
}
}
}
DelPcBuffInfo function:
void CSkill::DelPcBuffInfo(unsigned int tblidx)
{
p_BuffInfo.erase(tblidx);
}
Now after DelPcBuffInfo gets executed the program crash.
At this line it crash:
sBUFF_INFO* buff = it->second;
At debug:
Unhandled exception at 0x00578e0f in GameServer.exe: 0xC0000005:
Access violation reading location 0xddddddd9.
it + node_ = hash_ CXX0030; Error: expression cannot be evaluated
I dont really understand why this error appear..
edit:
If I add a "return" after this->DelPcBuffInfo(buff->tblidx) then the program dont crash..
Adding or removing items from a container will often invalidate your iterators. Check the documentation for unordered_map iterators or here: Iterator invalidation in boost::unordered_map
the correct idiom is
for( itertype(p_BuffInfo) it = p_BuffInfo.begin(); it != p_BuffInfo.end(); )
{
sBUFF_INFO* buff = it->second;
if(buff != NULL)
{
if(buff->dwTimeRemaining <= 0)
{
it = this->DelPcBuffInfo(buff->tblidx)
}else{
buff->dwTimeRemaining -= 2000;
it++;
}
}
}
ie dont increment in the loop. Instead increment if you dont delete otherwise have the delete operation return the new iterator. Thats why remove returns an iterator pointing at the next element
This is courtesy of the awesome Scott Myers
To add to the existing answers pointing out the erase-iterator idiom: The reason for you crash is that the iterator it is invalidated due to the removal of the element. Thus, the increment on the (invalid) operator causes undefined behaviour and it will point to some arbitrary memory block. Dereferencing the "iterator" then crashes your program.
To avoid this problem, apply the idiom as demonstrated in the other answers, that is
* Use the iterator version of erase. It returns an iterator to the next element ( which may be end())
* Use the return value of this erase as new value of it. Since it already points to the next element, do not increment again (otherwise you may skip an element in your map or cause undefined behaviour if it already points to the end of the map.
* Only increment the iterator yourself, when you did not erase an element.
Note: If your intention is to get rid of the sBUFF_INFO element completely upon removal from the map, your programm shows a memory leak. Erasing the pointer from the map does not delete the pointed-to memory. You need to delete the pointee yourself (or use an appropriate smart pointer).
void CSkill::DecreaseAllBuffRemTime()
{
auto it = p_BuffInfo.begin();
while( it != p_BuffInfo.end() )
{
sBUFF_INFO* buff = it->second;
if(buff)
{
if(buff->dwTimeRemaining <= 0)
{
// probably delete buff too
it = p_BuffInfo.erase(it);
} else {
buff->dwTimeRemaining -= 2000;
++it;
}
} else {
++it;
}
}
}
How can i delete all objects which are works finished
I using the following code but get list iterator not incrementable
How can I remove it without deleting it
list<A*> myList;
for(list<A*>::iterator it = myList.begin(); it !=myList.end(); ++it ){
(*it )->DoSomething();
if((*it )->WorksFnished()){
//myList.erase(it ); <- It's works but I get exception after the loop
//myList.remove(*it ); <- It's works but I get exception after the loop
}
}
erase returns an iterator
list<A*> myList;
list<A*>::iterator it = myList.begin();
while( it != myList.end() ) {
(*it)->DoSomething();
if( (*it)->WorksFnished() ) {
it = myList.erase(it);
} else {
++it;
}
}
You can make use of the fact that erase returns a new iterator, as described in other answers here. For performance-critical code, that might be the best solution. But personally, I would favor splitting the loop into separate processing and removal steps for readability and clarity:
// Assumes C++ 11 compatible compiler
list<A*> myList;
// Processing
for(const auto* each : myList){
each->DoSomething();
}
// Deletion
myList.remove_if([](A* each) {
return each->WorksFnished();
});
If you don't want to use remove_if, some alternatives are:
Copy all objects you want to keep into a new list, then std::swap it with your current list
Use a temporary list toBeRemoved, and add all objects that should be removed to that. When you're finished iterating over the actual list, iterate toBeRemoved and call myList.erase for each element
Some workaround..
increment the number of objects from the list that has WorkFnished.
then after the loop. if the accumulator match the list size, clear it.
size_t nFinished = 0;
list<A*> myList;
for(list<A*>::iterator it = myList.begin(); it !=myList.end(); ++it ){
(*it )->DoSomething();
if((*it )->WorksFnished()){
nFinished++;
}
}
if (nFinished == myList.size())
{
myList.clear();
}
If you use erase you have to assign it back to the iterator. In this case, we have to take care of the incrementing ourselves depending if the current element was erased or not.
list<A*> myList;
for (auto it = myList.begin(); it != myList.end(); )
{
(*it)->DoSomething();
if( (*it)->WorksFnished() ) {
it = myList.erase(it); // Sets it to the next element
} else {
++it; // Increments it since no erasing
}
}
std::list::erase
Return: 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.
I have a std::map<int,int> lets call it my_map
I iterate through this map using iterators and a for loop.
Within each iteration I want to modify many elements in this map but restore it again to its original values for next iteration of the loop.
I thought I could create a temporary copy of the iterator my_temp_map , but then I wouldn't be able to use the iterator to find the element I ought to be working on.
I then thought I could create a temporary copy, work on the origin my_map and at the end of each loop restore the original back to the temporary copy. However I believe this would invalidate the iterators as an assignment deletes all elements
How does one solve this problem?
Code added
So each inner loop will modify current_partition (and there is some more absent code that will store the result of the modified current_partition), but after each inner_loop I need current_loop to be restored to its former self.
std::map<int,int> current_partition = bitset_to_map(centre->second->bit_partitions);
int num_parts = (std::max_element(current_partition.begin(), current_partition.end(),value_comparer))->second;
for (std::map<int,int>::iterator itr = current_partition.begin(); itr != current_partition.end(); ++itr) {
for (int next_part = 0; next_part<num_parts+1; ++next_part) {
if (next_part != itr->second) {
int current_part = itr->second;
itr->second = next_part;
std::vector<int> first_changed_part, last_changed_part;
for (std::map<int,int>::iterator new_itr = current_partition.begin(); new_itr != current_partition.end(); ++new_itr) {
if (new_itr->second == current_part)
first_changed_part.push_back(new_itr->first);
if (new_itr->second == next_part)
last_changed_part.push_back(new_itr->first);
}
}
}
}
I think that std::advance may be of help. Create the temp, then advance begin() until you're where you are now (found out with std::distance)...then whatever it is you're trying to do.
With the code, I understand what you're going for now. I'd do it pretty much the first way you suggest: each time through the outer loop, make a temporary copy of the current_partition data structure, and then work on that, discarding it at the end.
You said that the problem with that would be that you couldn't use an iterator into the original map to find the element you ought to be working on. That's true; you can't do that directly. But it's a map. The element you're working on will have a key which will be the same in any copy of the data structure, so you can use that to create an iterator to the element that you ought to be working on in the copy.
For instance:
std::map<int,int> current_partition = bitset_to_map(centre->second->bit_partitions);
int num_parts = (std::max_element(current_partition.begin(), current_partition.end(),value_comparer))->second;
for (std::map<int,int>::iterator itr = current_partition.begin(); itr != current_partition.end(); ++itr) {
// Make a temporary copy of the map. Iterators between the original and the copy aren't
// interchangeable, but the keys are.
std::map<int,int> tmp_current_partition = current_partition;
// Use the iterator itr to get the key for the element you're working on (itr->first),
// and then use that key to get an equivalent iterator into the temporary map using find()
std::map<int,int>::iterator tmp_itr = tmp_current_partition.find(itr->first);
// Then just replace current_partition with tmp_current_partition and
// itr with tmp_itr in the remainder (hopefully I didn't miss any)
for (int next_part = 0; next_part<num_parts+1; ++next_part) {
if (next_part != tmp_itr->second) {
int current_part = tmp_itr->second;
tmp_itr->second = next_part;
std::vector<int> first_changed_part, last_changed_part;
for (std::map<int,int>::iterator new_itr = tmp_current_partition.begin(); new_itr != tmp_current_partition.end(); ++new_itr) {
if (new_itr->second == current_part)
first_changed_part.push_back(new_itr->first);
if (new_itr->second == next_part)
last_changed_part.push_back(new_itr->first);
}
}
}
}