vector::empty() bugged, or am I missing something? - c++

I've got this piece of code, which throws an out-of-range error.
if (!_enemyVec.empty())
{
for (std::vector<EnemyShip*>::size_type i = 0; i != _enemyVec.size(); i++)
{
if (!_projVec.empty())
{
for (std::vector<Projectile*>::size_type y = 0; y != _projVec.size(); y++)
{
===>_projVec[y]->CheckCollision(_enemyVec[i]);
if (_projVec[y]->Destroy())
DestroyProj(y);
if (_enemyVec[i]->Destroy())
DestroyShip(i);
}
}
}
}
Notice how I've got if (!_projVec.empty()) which should be "false" if the vector _projVec is empty.
The "===>" is where I get my out-of-range error(since _projVec is empty).
According to http://www.cplusplus.com/reference/vector/vector/empty/ vector::empty "Returns whether the vector is empty (i.e. whether its size is 0)."
However when I run the code it goes into the 2nd for-loop throwing an out-of-range error, while my vector is empty (it's size is 0)?
+ _projVec { size=0 } std::vector<Projectile *,std::allocator<Projectile *> >
I think I'm missing something so I'm wondering if anyone here can clear this up?
EDIT: Here are my DestroyProj/Ship functions.
void ObjectManager::DestroyShip(std::vector<EnemyShip*>::size_type &index)
{
delete _enemyVec[index];
_enemyVec[index] = nullptr;
_enemyVec.erase(_enemyVec.begin() + index);
index--;
AddScore(10);
}
void ObjectManager::DestroyProj(std::vector<Projectile*>::size_type &index)
{
delete _projVec[index];
_projVec[index] = nullptr;
_projVec.erase(_projVec.begin() + index);
index--;
}
As BWG pointed out, it shouldn't iterate if they are empty from the beginning, so the problem probably lies where I set the index back once. I also realise that this is probably a bad way to iterate through the vectors, so if someone can suggest me a different way to do it would be very much appreciated.
Please note that during this nested for-loop I want to be able to remove an item from both vectors.

You have two nested loops modifying two vectors. While erasing from the inner vector is fine, erasing an element from the outer vector, is not (assume the outer has one element, only)

Probably DestroyProj() (or one of the other functions) modifies _projVec. So even if it wasn't empty at the start of the loop, it might become empty when projects are removed.

You should iterate over a copy of the vectors, or you should remember which indexes to remove and then remove them at the end. Don't let DestroyProj and DestroyShip modify the vector.

Related

How to avoid out of range exception when erasing vector in a loop?

My apologies for the lengthy explanation.
I am working on a C++ application that loads two files into two 2D string vectors, rearranges those vectors, builds another 2D string vector, and outputs it all in a report. The first element of the two vectors is a code that identifies the owner of the item and the item in the vector. I pass the owner's identification to the program on start and loop through the two vectors in a nested while loop to find those that have matching first elements. When I do, I build a third vector with components of the first two, and I then need to capture any that don't match.
I was using the syntax "vector.erase(vector.begin() + i)" to remove elements from the two original arrays when they matched. When the loop completed, I had my new third vector, and I was left with two vectors that only had elements, which didn't match and that is what I needed. This was working fine as I tried the various owners in the files (the program accepts one owner at a time). Then I tried one that generated an out of range error.
I could not figure out how to do the erase inside of the loop without throwing the error (it didn't seem that swap and pop or erase-remove were feasible solutions). I solved my problem for the program with two extra nested while loops after building my third vector in this one.
I'd like to know how to make the erase method work here (as it seems a simpler solution) or at least how to check for my out of range error (and avoid it). There were a lot of "rows" for this particular owner; so debugging was tedious. Before giving up and going on to the nested while solution, I determined that the second erase was throwing the error. How can I make this work, or are my nested whiles after the fact, the best I can do? Here is the code:
i = 0;
while (i < AIvector.size())
{
CHECK:
j = 0;
while (j < TRvector.size())
{
if (AIvector[i][0] == TRvector[j][0])
{
linevector.clear();
// Add the necessary data from both vectors to Combo_outputvector
for (x = 0; x < AIvector[i].size(); x++)
{
linevector.push_back(AIvector[i][x]); // add AI info
}
for (x = 3; x < TRvector[j].size(); x++) // Don't need the the first three elements; so start with x=3.
{
linevector.push_back(TRvector[j][x]); // add TR info
}
Combo_outputvector.push_back(linevector); // build the combo vector
// then erase these two current rows/elements from their respective vectors, this revises the AI and TR vectors
AIvector.erase(AIvector.begin() + i);
TRvector.erase(TRvector.begin() + j);
goto CHECK; // jump from here because the erase will have changed the two increments
}
j++;
}
i++;
}
As already discussed, your goto jumps to the wrong position. Simply moving it out of the first while loop should solve your problems. But can we do better?
Erasing from a vector can be done cleanly with std::remove and std::erase for cheap-to-move objects, which vector and string both are. After some thought, however, I believe this isn't the best solution for you because you need a function that does more than just check if a certain row exists in both containers and that is not easily expressed with the erase-remove idiom.
Retaining the current structure, then, we can use iterators for the loop condition. We have a lot to gain from this, because std::vector::erase returns an iterator to the next valid element after the erased one. Not to mention that it takes an iterator anyway. Conditionally erasing elements in a vector becomes as simple as
auto it = vec.begin()
while (it != vec.end()) {
if (...)
it = vec.erase(it);
else
++it;
}
Because we assign erase's return value to it we don't have to worry about iterator invalidation. If we erase the last element, it returns vec.end() so that doesn't need special handling.
Your second loop can be removed altogether. The C++ standard defines functions for searching inside STL containers. std::find_if searches for a value in a container that satisfies a condition and returns an iterator to it, or end() if it doesn't exist. You haven't declared your types anywhere so I'm just going to assume the rows are std::vector<std::string>>.
using row_t = std::vector<std::string>;
auto AI_it = AIVector.begin();
while (AI_it != AIVector.end()) {
// Find a row in TRVector with the same first element as *AI_it
auto TR_it = std::find_if (TRVector.begin(), TRVector.end(), [&AI_it](const row_t& row) {
return row[0] == (*AI_it)[0];
});
// If a matching row was found
if (TR_it != TRVector.end()) {
// Copy the line from AIVector
auto linevector = *AI_it;
// Do NOT do this if you don't guarantee size > 3
assert(TR_it->size() >= 3);
std::copy(TR_it->begin() + 3, TR_it->end(),
std::back_inserter(linevector));
Combo_outputvector.emplace_back(std::move(linevector));
AI_it = AIVector.erase(AI_it);
TRVector.erase(TR_it);
}
else
++AI_it;
}
As you can see, switching to iterators completely sidesteps your initial problem of figuring out how not to access invalid indices. If you don't understand the syntax of the arguments for find_if search for the term lambda. It is beyond the scope if this answer to explain what they are.
A few notable changes:
linevector is now encapsulated properly. There is no reason for it to be declared outside this scope and reused.
linevector simply copies the desired row from AIVector rather than push_back every element in it, as long as Combo_outputvector (and therefore linevector) contains the same type than AIVector and TRVector.
std::copy is used instead of a for loop. Apart from being slightly shorter, it is also more generic, meaning you could change your container type to anything that supports random access iterators and inserting at the back, and the copy would still work.
linevector is moved into Combo_outputvector. This can be a huge performance optimization if your vectors are large!
It is possible that you used an non-encapsulated linevector because you wanted to keep a copy of the last inserted row outside of the loop. That would prohibit moving it, however. For this reason it is faster and more descriptive to do it as I showed above and then simply do the following after the loop.
auto linevector = Combo_outputvector.back();

Invalid pointer operation error when calling vector.erase() function

I am using vector::erase() function to delete the the first element in the vector until it is empty but my compiler is giving me an "Invalid Pointer Operation" error.
I have a vector of class-defined objects, foo, called bar.
I am deleting elements one by one like so:
for(int i = 0; i < bar.size(); i++){
if(!bar.empty()){
bar.erase(bar.begin());
}
}
When I run my program, it only completes one iteration (no matter the size) and breaks on the second.
Specifically, it breaks on the STL function _Destroy
template<class _TyDtor> inline
void _Destroy(_TyDtor _FARQ *_Ptr)
{ // destroy object at _Ptr
_DESTRUCTOR(_TyDtor, _Ptr);
}
*note I know there is a clear function that would do this more neatly but this is just a simplified example of something else that I am trying to do
As always when modifying a range while traversing the range, you cannot unconditionally increment the loop counter.
Consider the simple example of a two-element set. When i is 0, you remove the first element and increment i. Now i is 1 and bar.size() is 1, and the loop exits, failing to delete the second element.
The standard solution is to make the increment conditional on not modifying the container:
for (int i = 0; i < bar.size(); /* no increment here */) {
if (!bar.empty()) {
bar.erase(bar.begin());
} else {
++i;
}
}
When you do modify the container by erasing an element, i stays constant but the range moves one element closer to it. Also incrementing i would be double-counting.
(This is an extremely common mistake, though usually it's expressed in terms of iterators, e.g. here or here or here.)
The problem is the condition part of the loop bar.size() which keeps changing in each iteration i.e., decreasing in this example by 1.
It is difficult to say what exactly you are trying to achieve within the loop but if you want to execute the loop bar-size () times then use the follow logic:
const size_t vecSize = bar.size ()
for(int i = 0; i < vecSize ; i++)
{ ..... }
Otherwise if you want to execute the loop till bar-size () is empty, then use the while loop as follows:
while (bar.size > 0)
{ ..... }

Erase in multiset

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.

Vector erase function in for loop is not erasing vector of classes properly

I have a simple for loop:
for (int i = 0; i < c.numparticles; i++)
{
if ( labs((noncollision[i].getypos())) > 5000 )
{
noncollision.erase (noncollision.begin()+i);
}
}
Where noncollision is a vector of class particle. In this specific example, any noncollision which has a ypos greater than 5000 should be erased. I have been working with a noncollision size of 6, of which 2 have ypos much greater than 5000. However, this for loop is only erasing one of them, completely ignoring the other. My suspicion is that because noncollision is a vector of classes, that this classes is somehow protected, or causes the array function to act differently? Here is my declaration for noncollision, and for particle:
vector<particle> noncollision;
class particle{
private:
int xpos;
int ypos;
int xvel;
int yvel;
bool jc; // Has the particle just collided?
public:
etc....
};
Could anyone explain why this is happening, and how to rectify it? Do I somehow need to set up an 'erase function' for the particle class?
If you have two candidate elements next to each other (say, at i=5 and i=6), then you jump over the second, because you just erased the one at i=5... then the second becomes the new i=5 but you increment i to get i=6 on the next loop.
You need to fix your loop to properly support the fact that you're simultaneously removing elements from the same container over which you're iterating.
Typically you'd use actual iterators (rather than a counter i), and vector::erase conveniently returns a new iterator for you to use in the next iteration:
vector<particle>::iterator it = noncollision.begin(), end = noncollision.end();
for ( ; it != end; ) { // NB. no `++it` here!
if (labs(it->getypos()) > 5000) {
// erase this element, and get an iterator to the new next one
it = noncollision.erase(it);
// the end's moved, too!
end = noncollision.end();
}
else {
// otherwise, and only otherwise, continue iterating as normal
it++;
}
}
However, quoting Joe Z:
Also, since erase can be O(N) in the size of a vector, you might (a) benchmark the loop using reverse iterators too, (b) consider copying the not-erased elements into a fresh vector as opposed to deleting elements out of the middle, or (c) using a list<> instead of a vector<> if deleting from the middle is a common operation.
Or, if you're lazy, you could also just reverse your iteration order, which preserves the sanctity of your counter i in this specific case:
for (int i = c.numparticles-1; i >= 0; i--) {
if (labs(noncollision[i].getypos()) > 5000) {
noncollision.erase(noncollision.begin()+i);
}
}
Just be careful never to change i to an unsigned variable (and your compiler is probably warning you to do just that — i.e. to use size_t instead — if c.numparticles has a sensible type) because if you do, your loop will never end!
However, this for loop is only erasing one of them, completely ignoring the other.
This is because you are going front to back. When your code erases an item at, say, index 6, the item that was previously at index 7 is at the index 6 now. However, the loop is going to skip index 6 after i++, thinking that it has already processed it.
If you go back-to-front, the problem will be fixed:
for (int i = c.numparticles-1; i >= 0; i--)
{
if ( labs((noncollision[i].getypos())) > 5000 )
{
noncollision.erase (noncollision.begin()+i);
}
}
It looks like you're suffering from "Invalidated Iterator" syndrome, although in this case it's the index that's the problem.
Are the 2 elements you want to erase next to each other?
The problem is that erasing an element from a vector causes the remaining underlying elements to be copied to a new location (unless you erase the last element), and the number of elements in the vector is reduced by one.
Since you're using indexing into the vector, you're not falling foul of the first problem (which is iterators being invalidated), but:
you will never check the element immediately after the one you just erased
your indexing will spill off the end of the vector (undefined behaviour)
Modifying any sequence you're inspecting in the same loop is a bad idea. Have a look at
remove_if for a better way. This algo puts all the matching elements at the end of the vector, and returns you an iterator to the first one that was moved, allowing you to remove them all in one go safely.

Deleting elements from a vector

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.