I have a function that iterates through a vector and calls another function to execute its contents in some manner. As a result of that execution new elements could be added to the vector. Function code is as follows:
void foo() {
for (std::vector<Item*>::iterator it = item_list.begin(); it != item_list.end(); ++it ) {
if (/*some condition*/) {
bar(it);
}
}
}
While I was googling this problem I saw that iterator might get invalidated if resize happens, but the writer was not specific on why nor when or what is the proper way of handling this problem.
As a vector is random access, you can store the distance temporarily and re-create the iterator afterwards:
void foo() {
for (std::vector<Item*>::iterator it = item_list.begin(); it != item_list.end(); ++it ) {
if (/*some condition*/) {
const auto d = std::distance( item_list.begin(), it );
bar(it);
it = item_list.begin();
std::advance( it, d );
}
}
}
The answer assumes that new elements are added after the current position, e.g., at the end. It also assumes that it is desirable that the new elements are also part of the iteration, i.e., they will also be checked against some condition and bar will be called if they do match.
Instead of inserting more items on that vector within a interaction, simply create another vector to store all the items and do the resizing while you iterate in this one, then you discard the current one and swap for that updated vector.
That's the standard way to do that.
According to the documentation, if inserting an element changes the capacity of the vector, all iterators are invalidated.
To work around this, add new items to a temporary vector and merge the two after you're done.
Related
I have a member variable of this class that is set<pair<string,map<string,int> > > setLabelsWords; which is a little convoluted but bear with me. In a member function of the same class, I have the following code:
pair<map<string,int>::iterator,bool> ret;
for (auto j:setLabelsWords) {
if (j.first == label) {
for (auto k:words) {
ret = j.second.insert(make_pair(k,1));
if (ret.second == false) {
j.second[k]++;
}
}
}
}
"words" is a set of strings and "label" is a string. So basically it's supposed to insert "k" (a string) and if k is already in the map, it increments the int by 1. The problem is it works until the outermost for loop is over. If I print j.second's size right before the last bracket, it will give me a size I expect like 13, but right outside the last bracket the size goes back to 7, which is what the size of the map is initially before this code block runs. I am super confused why this is happening and any help would be much appreciated.
for (auto j:setLabelsWords) {
This iterates over the container by value. This is the same thing as if you did:
class Whatever { /* something in here */ };
void changeWhatever(Whatever w);
// ...
{
Whatever w;
changewhatever(w);
}
Whatever changewhatever does to w, whatever modifications are made, are made to a copy of w, because it gets passed by value, to the function.
In order to correctly update your container, you must iterate by reference:
for (auto &j:setLabelsWords) {
for (auto j:setLabelsWords) {
This creates a copy of each element. All the operations you perform on j affect that copy, not the original element in setLabelsWords.
Normally, you would just use a reference:
for (auto&& j:setLabelsWords) {
However, due to the nature of a std::set, this won't get you far, because a std::set's elements cannot be modified freely via iterators or references to elements, because that would allow you to create a set with duplicates in it. The compiler will not allow that.
Here's a pragmatic solution: Use a std::vector instead of a std::set:
std::vector<std::pair<std::string, std::map<std::string,int>>> setLabelsWords;
You will then be able to use the reference approach explained above.
If you need std::set's uniqueness and sorting capabilities later on, you can either apply std::sort and/or std::unique on the std::vector, or create a new std::set from the std::vector's elements.
I have been struggling to put a vector object into a project im doing
I have read what little i could find about doing this and decided to give it a go.
std::vector<BrickFalling> fell;
BrickFalling *f1;
I created the vector. This next piece works fine until i get to the erase
section.
if(brickFall == true){
f1 = new BrickFalling;
f1->getBrickXY(brickfallx,brickfally);
fell.push_back(*f1);
brickFall = false;
}
// Now setup an iterator loop through the vector
vector<BrickFalling>::iterator it;
for( it = fell.begin(); it != fell.end(); ++it ) {
// For each BrickFalling, print out their info
it->printBrickFallingInfo(brick,window,deadBrick);
//This is the part im doing wrong /////
if(deadBrick == true)// if dead brick erase
{
BrickFalling[it].erase;//not sure what im supposed to be doing here
deadBrick = false;
}
}
You can totally avoid the issue by using std::remove_if along with vector::erase.
auto it =
std::remove_if(fell.begin(), fell.end(), [&](BrickFalling& b)
{ bool deadBrick = false;
b.printBrickFallingInfo(brick,window,deadBrick);
return deadBrick; });
fell.erase(it, fell.end());
This avoids the hand-writing of the loop.
In general, you should strive to write erasure loops for sequence containers in this fashion. The reason is that it is very easy to get into the "invalid iterator" scenario when writing the loop yourself, i.e. not remembering to reseat your looping iterator each time an erase is done.
The only issue with your code which I do not know about is the printBrickFallingInfo function. If it throws an exception, you may introduce a bug during the erasure process. In that case, you may want to protect the call with a try/catch block to ensure you don't leave the function block too early.
Edit:
As the comment stated, your print... function could be doing too much work just to determine if a brick is falling. If you really are attempting to print stuff and do even more things that may cause some sort of side-effect, another approach similar in nature would be to use std::stable_partition.
With std::stable_partition you can "put on hold" the erasure and just move the elements to be erased at one position in the container (either at the beginning or at the end) all without invalidating those items. That's the main difference -- with std::stable_partition, all you would be doing is move the items to be processed, but the items after movement are still valid. Not so with std::remove and std::remove_if -- moved items are just invalid and any attempt to use those items as if they are still valid is undefined behavior.
auto it =
std::stable_partition(fell.begin(), fell.end(), [&](BrickFalling& b)
{ bool deadBrick = false;
b.printBrickFallingInfo(brick,window,deadBrick);
return deadBrick; });
// if you need to do something with the moved items besides
// erasing them, you can do so. The moved items start from
// fell.begin() up to the iterator it.
//...
//...
// Now we erase the items since we're done with them
fell.erase(fell.begin(), it);
The difference here is that the items we will eventually erase will lie to the left of the partitioning iterator it, so our erase() call will remove the items starting from the beginning. In addition to that, the items are still perfectly valid entries, so you can work with them in any way you wish before you finally erase them.
The other answer detailing the use of remove_if should be used whenever possible. If, however, your situations does not allow you to write your code using remove_if, which can happen in more complicated situations, you can use the following:
You can use vector::erase with an iterator to remove the element at that spot. The iterator used is then invalidated. erase returns a new iterator that points to the next element, so you can use that iterator to continue.
What you end up with is a loop like:
for( it = fell.begin(); it != fell.end(); /* iterator updated in loop */ )
{
if (shouldDelete)
it = fell.erase(it);
else
++it;
}
I'm new with STL containers, and right now i'm having some problems working with Multiset.
The problem is with the following two collections:
vector<DataReference*> referenceCol;
multiset<DataCount, DataCountSortingCriterion> orderedCol;
orderedCol mantains some data elements that have two public integer fields: id and count. I'm ordering that structure by the count elements. I may need to increment and decrement the count field from that elements, so, in order to maintain the ordering, i'm using a second collection (referenceCol) which is indexed by the id field and holds a reference (iterator) to the orderedCol collection, so every moment i need to refresh the count i can erase the element from orderedCol quickly (by refering to it in referenceCol), refresh it, and insert it again in its proper place according to the ordering.
The referenceCol is created in the constructor of my class, and has two fields: validReference (bool) that indicates whether the iterator reference is valid or not, and the multiset<....>::iterator variable.
The following methods handle the increment and decrement operations that affect these two collections:
void SomeClass::decrementCount(int index)
{
multiset<DataCount, DataCountSortingCriterion>::iterator it = referenceCol[index]->it;
DataCount dop = *it;
orderedCol.erase(it);
dop.count--;
if (dop.count > 0) {
it = orderedCol.insert(dop);
referenceCol[index]->it = it;
}
else {
referenceCol[index]->validRef = false;
}
}
void SomeClass::incrementCount(int index)
{
DataCount dop;
multiset<DataCount, DataCountSortingCriterion>::iterator it;
if (referenceCol[index]->validRef) {
it = referenceCol[index]->it;
dop = *it;
orderedCol.erase(it); <--------- BOOM!
dop.count++;
}
else {
dop.id = index;
dop.count = 1;
referenceCol[index]->validRef = true;
}
it = orderedCol.insert(dop);
referenceCol[index]->it = it;
}
The problem is that i'm having an error when i try to erase the iterator in the increment operation (look at the BOOM comment from the code).
The error i'm having is this:
"map/set erase iterator outside range"
The only thing that occurs to me is that maybe when erasing elements i may be invalidating other iterators, so those references doesn't hold any more, but i googled it and i found that for multiset, the erase operation only invalidate the erasing elements but no others...
I also checked that in my running example i'm not erasing the element with the problematic index.
Please help! And sorry for my bad english!
Oh, and i'm open to suggestions about better strategies to accomplish the "refresh" of elements in order :)
Thanks in advance!
With only the code you've given us to debug I cannot be certain, but I suspect that you are calling decrementCount(index) such that referenceCol[index]->validRef is false. When this happens your decrementCount method simply calls erase on the iterator without checking validity.
If this were to happen on a formerly invalidated iterator you might see the behavior you're seeing.
As an aside here it appears that you should be using a multimap not a multiset. But again without understanding all of your code I can't say that for sure.
The following C++ code fills a vector with a number of objects and then removes some of these objects, but it looks like it deletes the wrong ones:
vector<Photon> photons;
photons = source->emitPhotons(); // fills vector with 300 Photon objects
for (int i=0; i<photons.size(); i++) {
bool useless = false;
// process photon, set useless to true for some
// remove useless photons
if (useless) {
photons.erase(photons.begin()+i);
}
}
Am I doing this correctly? I'm thinking the line photons.erase(photons.begin()+i); might be the problem?
Definietly the wrong way of doing it, you never adjust i down as you delete..
Work with iterators, and this problem goes away!
e.g.
for(auto it = photons.begin(); it != photons.end();)
{
if (useless)
it = photons.erase(it);
else
++it;
}
There are other ways using algorithms (such as remove_if and erase etc.), but above is clearest...
the elegant way would be:
std::vector<Photon> photons = source->emitPhotons();
photons.erase(
std::remove_if(photons.begin(), photons.end(), isUseless),
photons.end());
and:
bool isUseless(const Photon& photon) { /* whatever */ }
The proper version will look like:
for (vector<Photon>::iterator i=photons.begin(); i!=photons.end(); /*note, how the advance of i is made below*/) {
bool useless = false;
// process photon, set useless to true for some
// remove useless photons
if (useless) {
i = photons.erase(i);
} else {
++i;
}
}
You should work with stl::list in this case. Quoting the STL docs:
Lists have the important property that insertion and splicing do not invalidate iterators to list elements, and that even removal invalidates only the iterators that point to the elements that are removed.
So this would go along the lines of:
std::list<Photon> photons;
photons = source->emitPhotons();
std::list<Photon>::iterator i;
for(i=photons.begin();i!=photons.end();++i)
{
bool useless=false;
if(useless)
photons.erase(i);
}
Erasing elements in the middle of a vector is very inefficient ... the rest of the elements need to be "shifted" back one slot in order to fill-in the "empty" slot in the vector created by the call to erase. If you need to erase elements in the middle of a list-type data-structure without incurring such a penalty, and you don't need O(1) random access time (i.e., you're just trying to store your elements in a list that you'll copy or use somewhere else later, and you always iterate through the list rather than randomly accessing it), you should look into std::list which uses an underlying linked-list for its implementation, giving it O(1) complexity for modifications to the list like insert/delete.
I have a method to which a vector's iterator is passed.
In this method I'd like to add some elements into the vector, but I am not sure whether this is possible when having only the iterator
void GUIComponentText::AddAttributes(vector<GUIComponentAttribute*>::iterator begin, vector<GUIComponentAttribute*>::iterator end)
{
for (vector<GUIComponentAttribute*>::iterator i = begin; i != end; ++i)
{
GUIComponentAttribute &attrib = *(*i);
// Here are the GUIComponentAttribute objects analyzed - if an object of a
// special kind appears, I would like to add some elements to the vector
}
}
Thanks
Markus
In the code you show, this is not possible. Especially because you should not add/remove elements to/from a vector while you iterate over it.
This is a long standing design "issue" in the STL. Iterators do not allow the modification of the structure of the underlying sequence they are iterating over: ie you can modify (sometimes) the elements themselves, but you cannot add/remove elements. Though InputIterator and OutputIterator are a bit special in this regard... hum...
This is actually the cause of the erase/remove idiom:
vec.erase(std::remove_if(vec.begin(), vec.end(), predicate), vec.end());
So, no, sorry, there is no way to actually modify the vector.
However, as exposed above, you can perfectly use the remove_if algorithm and simply return the new end of the valid range... or you can ask for the whole vector to begin with.
As noted by Björn, modifying a sequence structure while iterating over it is error-prone.
First, you'll have to change the interface. Given two iterators,
there's no way to get back to the container to which they refer; so if
you want to modify the container, you'll have to pass a reference to it,
i.e.:
void GUIComponentText::AddAttributes(
std::vector<GUIComponentAttribute*>& attributes )
{
for ( std::vector<GUIComponentAttribute*>::iter = attributes.begin();
iter != attributes.end();
++ iter )
{
// ...
}
}
Having done that: insertion can invalidate iterators. So it depends on
where you want to insert. If you want to insert at the current
position: std::vector<>::insert of a single element returns an
iterator to that element, which was inserted before your element, so you
can assign it to your iterator, adjust (if necessary), and continue:
iter = attributes.insert(iter, newAttribute);
++ iter; // Return to where we were...
If you're appending (push_back), the problem is a bit more complex;
you need to calculate the offset, then reconstruct the iterator:
size_t offset = iter - attributes.begin();
attributes.push_back( nweAttribute );
iter = attributes.begin() + offset;
In this case, it is probably simpler to iterate using a size_t and
[], rather than an iterator.
It is not possible to add elements into a vector whilst iterating over it. In addition, you most certainly cannot add one to a vector with just a pair of iterators- you'd need a pointer/reference to the whole vector object.
The best you could do is return a vector of new components to add by the the calling function.