I have to write a function that erases an element out of the list if it's bigger than the previous element.(The previous element is the one which points to the next element before deletion)
I think I've basically finished it but I don't know why it doesn't erase 5 out of my list.
void deleteBigger(list<int> s){
list<int>::iterator test;
for(test = s.begin(); test != s.end(); test++){
int sk1=*test;
cout<<sk1<<endl;
test--;
int sk2=*test;
cout<<sk2<<endl;
if(sk1>sk2){
cout<<"Im here!\n";
s.erase(test);
}
test++;
}
}
It doesn't give an error or anything it just doesn't erase. I tried to test the erase method in the main function of the program, and there it worked fine.
There are three problems with your code:
Your list is passed by value, not reference. So you are changing a copy of your list and it doesn't alter the original container
You try to remove an element from a list while iterating it. Edit: As #Remy Lebeau mentioned in the comments, to be more precise it's a problem because you don't update the iterator properly, but not a problem on its own. Be advised, that when you remove an element from a list, the iterator which pointed to the erased element is considered invalidated.
Upon the first iteration, you decremented the iterator out of the container's bounds
Summing it up, what you might want to have here looks something like this:
void deleteBigger(std::list<int> &s) {
using namespace std;
if (s.empty()) {
return;
}
for(auto test = next(s.cbegin()); test != s.cend(); ++test){
while ((*test > *prev(test)) && (test != s.cend())) {
test = s.erase(test);
}
}
}
I've copied your code and it doesn't work. The problem is your iterator pointer "test". You can't degree a pointer at the begin. The only thing you can do is use a control.
Note: it's wrong decrement a pointer because you are decrementing of (32 bits) the index of memory. In this case there is overriding -- operator that saves your program but be careful next times
Control your program. It's important use a debugger editor where you can stop the program at certain point and control the value of the variables
Related
I'm working with iterators on a set of elements of size greater that three almost all the time, but it happens that the generated set contains only one element, in this case, the following loop:
for(i = data_set.begin(); i != data_set.end(); i++)
{
//do something with the data
}
will never be entered even though "data_set" is not empty because data_set.begin()==data_set.end()
I'm doing a test to handle this particular case alone but the code is turning to a mess and is no longer clean.
What should be done to handle this properly?
Thanks,
자스민
If the set contains only 1 element, then:
std::next( data_set.begin() ) == data_set.end(), because begin() iterator points at first element of the container, and end() points to the element that is next after the last one.
I currently have a problem with vector.erase().
vector<gameObject> gameObjects;
for (auto it = gameObjects.end() - 1; it != gameObjects.begin();)
{
if ((it)->getDestroyed()) {
it = gameObjects.erase(it);
}
else {
--it;
}
}
So gameObject is the base class for everything inside the game and it has a bool flag that basically tells us if the object was destroyed. If the flag is set it should be removed from the vector.
class gameObject
{
protected:
bool toBeDestroyed;
public:
bool getDestroyed();
void markToDestroy();
};
Now the first destroyed object gets removed from the vector successfully and then I get get an error that iterator is not dereferencable, pointing to the vector library at line 73(?).
I then check with the msvc debugger. In the data preview it shows that iterator points to the last/newest element of gameObjects. It is then removed (erase(it)) and AFTERWARDS the data preview doesn't change and calling it->getDestroyed() results in the error message.
Debug assertion failed! vector iterator not dereferencible.
PS: I checked cplusplus.com and vector.erase should return a new, valid iterator so I'm not sure where I'm messing it up.
€: After I was told about the erase-remove idiom I went ahead and ended up with the following, which doesn't compile. Due to my function being a member of gameObject I'm not sure how to successfully call remove_if. Thanks
gameObjects.erase(remove_if(gameObjects.begin(), gameObjects.end(), gameObject::getDestroyed), gameObjects.end());
€2: A lot of you pointed out the first object isn't being checked. I propably should've pointed that out but the first element is ALWAYS the player and shouldn't be removed. Thanks for your comments nevertheless. I'll try with a simple forward loop without getting too fancy ^^.
€3: I tried Jonathan Mees suggested code but I get the exact same error message. I'll try and find out where exactly it happens but I can't just put a breakpoint into the erasing part anymore. Will mess around a bit.
€4: Problem was solved by removing the else {} condition and always decrementing the iterator. Thanks again for all your replies.
Let's say you have 2 objects in your vector and the last one is is marked as destroyed. When you call erase, it will return a new, valid iterator pointing at the element after the erased element. There is no element after the erased element, so the returned iterator is gameObjects.end(). You then continue to the top of the loop and dereference this iterator, which is not valid. You need to decrement your iterator after the erase if you want it pointing at a valid element.
One other note: If you ever wanted your first element removed, it will not be. Your loop exits when the iterator == gameObjects.begin(), so the first element is never checked.
Is there some reason you wanted to do this in reverse? If there is no specific reason, I would recommend you use the method recommended by #Borgleader.
Your loop is a little messed up - you're iterating backwards, ignoring the first element, and testing some elements multiple times. Might I suggest rbegin() as an alternative?
vector::erase returns the:
Iterator following the last removed element. If the iterator pos refers to the last element, the end() iterator is returned.
Meaning that vector::erase will never return vector::begin (unless you removed the only element in the container.) So it will always be dereferenced again after vector::erase is called. It will be dereferenced even if vector::end was returned by the call to vector::erase which is of course illegal.
Instead of this loop, consider using remove_if which is designed for this purpose:
gameObjects.erase(remove_if(begin(gameObjects),
end(gameObjects),
[](const auto& i){ return i.getDestroyed(); }), end(gameObjects));
EDIT:
I noticed you try to use this in your edit. You cannot use a bare function pointer as the predicate. If you want to avoid a lambda, you should consider the use of mem_fn:
gameObjects.erase(remove_if(begin(gameObjects),
end(gameObjects),
mem_fn(&gameObject::getDestroyed)), end(gameObjects));
Live Example
If there's difficulty in reading that line feel free to use as many variable as you like:
auto p = mem_fn(&gameObject::getDestroyed);
auto result = remove_if(begin(gameObjects), end(gameObjects), p);
gameObjects.erase(result, end(gameObjects));
Okay, a small problem with, hopefully, a quick, simple solution.
In my school textbook, in a chapter about the STL, it gives a simple sample program to input for using lists and for using an iterator with a list, like so:
#include <list>
#include <iostream>
#include <string>
using namespace std;
int main()
{
list<int> myIntList;
// Insert to the front of the list.
myIntList.push_front(4);
myIntList.push_front(3);
myIntList.push_front(2);
myIntList.push_front(1);
// Insert to the back of the list.
myIntList.push_back(5);
myIntList.push_back(7);
myIntList.push_back(8);
myIntList.push_back(9);
// Forgot to add 6 to the list, insert before 7. But first
// we must get an iterator that refers to the position
// we want to insert 6 at. So do a quick linear search
// of the list to find that position.
list<int>::iterator i = 0;
for( i = myIntList.begin(); i != myIntList.end(); ++i )
if( *i == 7 ) break;
// Insert 6 were 7 is (the iterator I refers to the position
// that 7 is located. This does not overwrite 7; rather it
// inserts 6 between 5 and 7.
myIntList.insert(i, 6);
// Print the list to the console window.
for( i = myIntList.begin(); i != myIntList.end(); ++i )
cout << *i << " "; cout << endl;
}
Now, at the line that says
list<int>::iterator i = 0;
I get an error in VS 2015 that says:
no suitable constructor exists to convert from"int" to "std::_List_iterator<std::_List_val<std::_List simple_types<int>>>"
What is the problem with the code presented, what is the solution, and why is this a problem to begin with? <-(I'll even settle with a simple grammatical error).
The value 0 may not be a valid value for an iterator. Try either removing the assignment or assigning the iterator to myIntList.begin().
The iterator may not be able to be treated as an index, like with a vector or array. Usually linked lists are not accessed by index; you have to traverse from the beginning.
What is the problem with the code presented
A simple typo in the example.
what is the solution
Replace this line:
list<int>::iterator i = 0;
With this instead:
list<int>::iterator i;
why is this a problem to begin with?
You cannot initialize an iterator with an integer value. Only the container knows what its iterators refer to, so only the container can initialize them. All you can do is request an iterator from a container, assign an iterator to another iterator, and increment/decrement/dereference an iterator. That is all.
From http://www.cplusplus.com/reference/iterator/:
An iterator is any object that, pointing to some element in a range of elements (such as an array or a container), has the ability to iterate through the elements of that range using a set of operators (with at least the increment (++) and dereference (*) operators).
This means that an iterator should be able to do the following:
Return the object it's currently "pointing" to (using the * operator)
Change itself to "point" to the next object in its list (using the ++ operator)
A reason for the existence of the iterator as a data type is to create a general way of interacting with different kinds of lists. However, this means that different lists will implement their iterators differently.
In many circumstances, initializing an iterator to a number doesn't make sense because of the implementation under the hood. As a result, we don't define an assignment operator with our iterator type std::vector<int>::iterator on the left and an int on the right. So when you try to assign your iterator to an integral value, list<int>::iterator i = 0; your compiler throws an error.
Let's look at an example where assigning an iterator to 0 doesn't make sense. You could implement an iterator for std::vector<int> as a pointer to an element in your vector. In this case:
* dereferences the pointer stored in vector<int>::iterator and returns its value.
++ modifies the pointer stored in vector<int>::iterator to point at the next element in the list.
However, assigning this pointer to 0 would be the same as assigning it to NULL, and dereferencing it no longer returns a valid element in your vector. (In fact, dereferencing NULL will cause an error!)
To avoid this error, simply make sure that you always assign your iterator to a value of the same type. In the STL, this is usually accomplished by using .begin() to return an iterator that points to the first element in your list.
I am having a problem while looping thru a map (std::map).
Inside my loop, there is a call to a function which sometimes (not always) erases elements of this same map. After this function is used, there is some code which is using some of this map information as input.
I am having no problems after this function erases any elements, except on the unique case that the last element of the map is erased.
My loop semms not to understand that the last element of the map is not the same as when it started to operate, and will try to operate on elements which doesnt exist, creating a crash.
It seems to me that the myMap.end() call on the loop description is not able to update itself with the new end() of the map.
The relevant part of the code is listed below:
for(std::map<int, ConnectionInfo>::iterator kv = myMap.begin(); kv != myMap.end(); ++kv) {
int thisConnectionID=kv->first; //This is where I get garbage when the loop enters when it shouldnt;
ConnectionInfo currentConnectionInfo=kv->second; //This is where I get garbage when the loop enters when it shouldnt;
status=eraseSomeMapElementsIfNecessary(thisConnectionID,currentConnectionInfo.DownPacket); //this function might erase elements on myMap. This generates no problems afterwards, except when the end element of myMap is erased
... //Next parts of the code make no further usage of myMaps, so I just hid it not to pollute the code
}
Is my interpretation that the kv != myMap.end() is not being able to understand that the inner loop is changing (erasing) the last element (end) of myMap?
In this case, how can I fix this issue?
Or is my interpretation wrong and the solution has nothing to do with what I stated before?
Thanks for your help!
The usual idiom when iterating a map with possibly deleting element is:
for(auto it = map.begin(); it != map.end(); ) {
if ( *it == /*is to delete*/ ) {
it = map.erase(it);
}
else
++it;
}
if your eraseSomeMapElementsIfNecessary might erase some random values in map being iterated then this will for sure cause problems. If element to which it is referencing was erased, it becomes invalid, then incrementing it with ++it is also invalid.
The problem is actually only with the it iterator, if eraseSomeMapElementsIfNecessary erases it and then you use it - you have Undefined Behaviour (UB). So the solution is to pass current iterator to eraseSomeMapElementsIfNecessary, and return from it the next one to iterate:
it = eraseSomeMapElementsIfNecessary(it);
the body of the for loop from my example should be inside your eraseSomeMapElementsIfNecessary function. At least this is one solution.
I am having no problems after this function erases any elements, except on the unique case that the last element of the map is erased.
Erasing an element in any container invalidates the iterator to it. After that you increment the invalidated iterator.
You should increment the iterator before you delete the element pointed by it.
If you do not know what elements that function inside the loop erases assume that all iterators are invalidated.
Maybe these 2 links will help:
How can I delete elements of a std::map with an iterator?
https://stackoverflow.com/a/8234813/3464942
Basically, what it all boils down to, is that you must update the iterator before it becomes invalid.
You have to preserve the next iterator before erasing the current one; since the current one will be invalid after deleting the element.
auto nextit = it+1;
map.erase(it);
it = nextit;
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;
}