I've been working a C++ assignment (I'm a novice). I'm supposed to add instantiated structs with string members to a list alphabetically (not allowed to use sorting mechanisms).
While I've got most of the functionality figured out I'm stuck at this:
void insertWord(vector<word_entry>& v, word_entry& we){
for(vector<word_entry>::iterator it = v.begin(); it != v.end();++it){
int determine = it->word.compare(we.word); //**1
if(determine > 0){ //**2
v.insert(it, we);
}
}
v.push_back(we);
}
Apologies in advance if the code is unconventionally written. Anyway, what I'm trying to do is to insert the object at the iterator's position - before the object to which the iterator is pointing (**1) if the if-return (**2) returns true.
Any thoughts? :(
Inserting into the vector causes any existing iterators to be be invalidated. The next time you try to increment the iterator, you have undefined behavior. To fix this, you can use
it = v.insert(it, we);
Related
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.
std::vector<struct::event>::iterator it;
std::vector<struct::event>::iterator last=myvector.end();
for (it=myvector.begin(); it<=last; it++){
if(mysignal.declination<(*last).declination){
if (mysignal.declination>=(*it).declination && mysignal.declination<(*(it+1)).declination){
myvector.insert(it+1, mysignal);
break;
}
}
if (mysignal.declination>=(*last).declination){
myvector.push_back(mysignal);
break;
}
}
I have a vector called myvector with events that are sorted with the declination. now I want to add mysignal to this vector on the right place. but i always get a seg fault after a few events which refers to: if(mysignal.declination<(*last).declination). I just can't see what is wrong.
Your loop is wrong, read the docs:
Returns an iterator to the element following the last element of the container.
This element acts as a placeholder; attempting to access it results in undefined behavior.
You can't dereference end(), it provides a way of knowing that you have overrun the container, so your loop condition should be it != myvector.end(), and last is wrong as well.
As others have said, C++ iterators define a half-open interval
('[begin()...end())'), which is what you should probably be
using in most other cases as well. And although it works with
iterators from a vector, in general, iterators do not support
<= (nor <); the standard idiom for a loop is:
for ( auto current = container.begin();
current != container.end();
++ current ) ...
(In the most likely case that you cannot count on C++11, you'll
have to write out the full iterator type, rather than use
auto. Although auto is one of the few things from C++11
that seems to work with VC++11 and with recent versions of
g++, so if those are the only targets you're concerned with, and
you can be sure of always having very recent versions, you can
use it.)
Also, if you want to access the last element of the vector in
the loop, myvector.back() will return a reference to it.
(myvector.back() is undefined behavior if the vector is empty,
but if the vector is empty, you won't enter the loop.)
end() does not refer to the last element in the container, you need to change your condition as follows.
for (it=myvector.begin(); it != last; it++){
You have other broken logic as well that is dereferencing last that you need to fix.
I am getting this error when trying to iterate over a map that is pointed to by another object. It works when I am not using a pointer. (Iterating over the member map "pieces") I am therefore wondering what to do, or if it's not possible to iterate through the map like this ? :
Board * Board::ccBoard(){
Board * newBoard = new Board();
map<Vec2, Piece>::iterator it;
for (it = newBoard->pieces.begin(); it != newBoard->pieces.end(); ++it)
newBoard->removePiece(it->first);
return newBoard;
}
Thanks in advance!
The removePiece() function removes the element that it is referring to, invalidating it. An attempt is then made to increment it resulting in the assertion failure. From map::erase():
References and iterators to the erased elements are invalidated.
I am unsure what the intention of the for loop is, it appears that it would effectively empty the map in which case just use map::clear():
newBoard->pieces.clear();
To fix, get rid of the ++it in the for loop and replace it->first by it++->first.
(This will increment the iterator and call erase() using a copy.)
I have a vector that holds pointers to abstract type Rock:
vector<Rock*> rocks;
If I loop through the vector with an iterator and then try to access the object (a non-abstract class that extends Rock) via the iterator, I get an 'EXC_BAD_ACCESS' error in XCode 4:
vector<Rock*>::iterator rockIter;
for (rockIter = rocks.begin(); rockIter != rocks.end(); ++rockIter)
{
bullet.hit(*(*rockIter));
}
But looping through it normally like is no problem:
for (int i = 0; i < rocks.size(); i++)
{
bullet.hit(*rocks[i]);
}
The function hit() looks like:
bool Bullet::hit(Rock & rock)
I thought that *(*rockIter) and *rock[i] would do the same thing but clearly they dont. What is different, and how can I pass a reference of an object in the vector via an iterator like I do with *rock[i]?
Without seeing more code I'm not sure if this is the problem, but is the code in bullet.hit() changing the vector by adding elements? If it is and you're iterating over it using an iterator, that iterator will get invalidated and any future dereferences on it will result in undefined behavior. However, using the standard int-based for loop will not have this problem, because accesses by index can't be invalidated.
Hope this helps!
Following the help in this question, I am using a reference to my Class 'Mover' to manipulate the object (as part of a set) in a vector. I am having issues however, and I cannot seem to identify what's causing it for sure. It appears that once I've reached 30-35 objects in my vector (added at pseudo-random intervals) the program halts. No crash, just halt, and I have to manually end the task (CTRL-C doesn't work).
My problem appears to lie in these bits of code. My original:
int main() {
std::vector< Mover > allMovers;
std::vector< Mover >::iterator iter = allMovers.begin();
//This code runs to the end, but the 'do stuff' lines don't actually do anything.
Mover tempMover;
//Other code
while(iter < allMovers.end()) {
tempMover = *iter;
//Do stuff with tempMover
//Add another tempMover at a random interval
allMovers.push_back(CreateNewMover());
iter++;
}
//Other code
}
My update after the previous question linked to above:
int main() {
std::vector< Mover > allMovers;
std::vector< Mover >::iterator iter = allMovers.begin();
//This code crashes once about 30 or so items exist in the vector, but the 'do stuff' lines do work.
//Other code
while(iter < allMovers.end()) {
Mover& tempMover = *iter;
//Do stuff with tempMover
//Add another tempMover at a random interval
allMovers.push_back(CreateNewMover()); //Crashes here.
iter++;
}
//Other code
}
Any ideas of how to track this down? I have std::couts all over the place to flag where the code is for me. The crash (while happens at a varied number of objects) always crashes on the push_back(), despite having worked successfully multiple times in the same run before the crash.
EDIT
While I accept and (think) I understand the answer re: iterators, what I don't understand is why the code DOES work completely when I am not using a reference to the object? (First code block).
Another EDIT
In case anyone was looking for this specifically, part of my question was not addressed: "How to debug?" As a C++ newbie, I was unaware of the gdb debugger (using MinGW). Now that I've learned about it, it has been very helpful in finding the source of these issues.
When a vector reallocates its memory, all iterators are invalidated (along with any reference or pointer to any element). So sometimes your push_back will invalidate iter, and trying to use it afterwards gives undefined behaviour.
The simplest fix is to use an index rather than an iterator. Alternatively, if you can calculate an upper bound for the maximum size of the vector, you could call reserve before the loop to ensure it never reallocates. Or you could use std::list, whose iterators are preserved when new elements are inserted.
UPDATE: Regarding your edit, both give undefined behaviour. It might be that, in the first case, you don't crash because you don't access a dangling reference (while accessing tempMover in the second might very well crash), and then the memory happens to be reallocated at a lower address than before, so the while condition (which uses < rather than the more conventional !=) exits the loop immediately. Or something completely different could be happening - that's the nature of undefined behaviour.
You are (probably) doing it wrong.
The thing is, mixing iteration over a container and manipulation of the container structure (here adding objects) is extremely error-prone.
Whenever you add an element in allMovers, there is a risk that iter is invalidated. Any usage of iter after it has been invalidated is Undefined Behavior.
It is possible to do it correctly:
iter = allMovers.insert(allMovers.end(), CreateNewMover());
however it's just a bad idea in general.
My advice would be to ban this kind of code from your code base altogether. Every single occurrence is a bug in the making. Find another algorithm.
From documentation for push_back():
If new size() is not larger than capacity(), no iterators or references are invalidated. Otherwise all iterators and references are invalidated.
When you reach 30 or some objects new size() > capacity(), resulting in invalidation of the iterator iter, which is derefenced causing undefined behaviour.
You might probably need to change the line containing the while statement:
while(iter != allMovers.end()) {
the < operator seems to work fine with a vector usually, but I had better results using != which works with other containers and also seems to be used in more example code out there.
Update
You may replace the while loop with an equivalent for loop like this:
for(std::vector<Mover>::iterator iter = allMovers.begin(); iter != allMovers.end(); ++iter)
{
This has the advantage that the increment of the iterator iter "has its place" and is less likely to be forgotten.
Update 2
If I understand your example above, you'd like to fill the container with some content. I suggest (as others did) to get rid of the iterator altogether.
int main()
{
std::vector< Mover > allMovers;
//Other code
while(1) // this loop will add new movers as long as it succeeds to create one
{
Mover new_mover = CreateNewMover();
if ( IS EMPTY (new_mover) ) // pseudocode. Check if the previous
break; // CreateNewMover() succeeded.
allMovers.push_back(new_mover);
}
//Other code
}