erase max element from STL set - c++

This is a follow-up on a previous question I had ( Complexity of STL max_element ).
I want to basically pop the max element from a set, but I am running into problems.
Here is roughly my code:
set<Object> objectSet;
Object pop_max_element() {
Object obj = *objectSet.rbegin();
set<Object>::iterator i = objectSet.end()--; //this seems terrible
objectSet.erase(i); //*** glibc detected *** free(): invalid pointer
return obj;
}
Earlier I tried objectSet.erase(objectSet.rbegin()); but the compiler complained that there was no matching function (I'm guessing it doesn't like the reverse_iterator). I know there is no checking for an empty set, but it's failing when objectSet.size() >> 0.

You're pretty close, but you're trying to do a little too much in that iterator assignment. You're applying the post-decrement operator to whatever end returns. I'm not really sure what that does, but it's almost certainly not what you want. Assign the result of end to i, and then decrement it to get the last element of the set.
set<Object>::iterator i = objectSet.end();
--i;
Object obj = *i;
objectSet.erase(i);
return obj;

You need to do this:
set<Object> objectSet;
Object pop_max_element() {
Object obj = *objectSet.rbegin();
set<Object>::iterator i = --objectSet.end(); // NOTE: Predecrement; not postdecrement.
objectSet.erase(i); //*** glibc detected *** free(): invalid pointer
return obj;
}

The statement
set<Object>::iterator i = objectSet.end()--;
means 'assign end() to i then decrement a temporary variable that is about to be thrown away'. In other words it's the same as set<Object>::iterator i = objectSet.end();, and I'm sure you recognise you cannot erase end(), because it points to one past the end. Use something like this instead:
assert(!objectSet.empty()); // check there is something before end
set<Object>::iterator i = objectSet.end();
--i;
objectSet.erase(i);
and that's okay, it's a legitimate way to essentially reproduce .back() for a set.
Also, reverse iterators have a base() member to convert to a normal iterator and I guess you can only erase normal iterators - try objectSet.erase(objectSet.rbegin().base()).

Related

C++ Vector.erase() last element corrupts iterator

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));

Why do I get a segfault error (C++ 98)?

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);

c++ vector object .erase

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;
}

std::multiset::iterator = NULL no longer valid?

I have some code that I'm updating to C++11 using gcc 4.7 (from 3.1)
I have a multiset defined as a private member of a class:
multiset <Object*, objectcomp> objects_;
In the code is a segment that looks like this (p_q is a pair of multiset iterators, sorry about that nasty line, can't wait to replace that with auto, haha):
void Terrain::removeObject(Object* obj){
pair<multiset<Object*, objectcomp>::iterator, multiset<Object*, objectcomp>::iterator> p_q;
multiset<Object*, objectcomp>::iterator p,q;
q = NULL;
p_q = objects_.equal_range(obj);
for(p = p_q.first; p != p_q.second; p++){
if(*p == obj) {q=p; break;}
}
if(q!=NULL){
... do stuff based on q no longer being null
}
}
This won't compile anymore. Can you not set iterators to null anymore? What is the alternative? (nullptr doesn't work either)
It was never legal to set iterators to NULL. You may have gotten lucky because your particular implementation happened to use pointers as iterators for that type, but it was still illegal.
The right answer is:
q = objects_.end();
Or, in some cases:
q = multiset<Object*, objectcomp>::iterator();
You never could set an iterator to NULL. If the above code ever worked, it was by sheer accident. Given any reasonable implementation of multiset, it's hard to see how it could ever have compiled, let alone run.
The best way to get a "nowhere" iterator is to use the end of the container. Replace q = NULL with q = objects_.end().
Also, never put raw pointers in a container; it's an open invitation to memory leaks. You almost certainly want either multiset<Object,comp> or multiset<shared_ptr<Object>,comp>.

Can a C++ map contain a pointer to my arbitrary class?

I am writing a small game engine as a summer project, and am struggling a little with the STL map.
I have declared a class RenderList to hold objects. The RenderList will be passed to a Renderer class to do the work.
The RenderList has a map<std::string,Entity*> objects;
That all works, till I try to obtain an Entity* from the map and I get:
Assertion failed, in vc/include/xtree
Expression: map/set iterator not dereferencable.
This is the code to retrieve the pointer.
Entity* RenderList::getByName(std::string str){
return objects.find(str)->second;
}
I need it to hold a pointer and not the actual object, as there are different sub-classes of Entity which I'll need.
I am fairly new to the STL, should I not store pointers in a map?
Surely I should be allowed to do this, or is it a better idea to store objects instead?
And finally, am I simply doing it wrong!?
Hope this question is not a duplicate, I did do a quick search beforehand. Also if this would be better in the GameDev Stack I'll post it there.
If the key is not found then map::find(key) returns a "past-the-end" iterator, i.e. the value returned by map::end(), and that iterator doesn't point to any element so can't be dereferenced. You do not check what find returns before dereferencing it.
Are you sure they key is in the map?
You probably want to do something like return NULL if the key isn't found, which you can check for by comparing the returned iterator to end e.g.
Entity* RenderList::getByName(std::string str){
map_type::iterator it = objects.find(str);
if (it == objects.end())
return NULL;
return it->second;
}
Where RenderList defines the typedef:
typedef map<std::string,Entity*> map_type;
(N.B. I always make my classes define typedefs for the containers I use as implementation details, because it's much easier to write map_type::iterator than map<std::string,Entity*>::iterator and because if I change the container to something else I don't have to change all the code using it to e.g. map<std::string,shared_ptr<Entity>>::iterator I can just leave it as map_type::iterator and it still works perfectly.)
Regarding the general design, can you store a boost::shared_ptr<Entity> or std::tr1::shared_ptr<Entity> instead of a raw pointer? It will be much safer and simpler to manage the object lifetimes.
That probably means that the name you were looking for does not exist in the map. If the key does not exist, find method return the end iterator for the map, which is indeed not dereferencable.
If the "not found" situation is something that can naturally happen, then you can do this
Entity* RenderList::getByName(std::string str){
map<std::string,Entity*>::iterator it = objects.find(str);
return it != objects.end() ? it->second : NULL;
}
thus passing on the responsibility to handle this situation to the caller.
If the "not found" situation is not supposed to happen, either throw an exception or at least do
assert(it != objects.end());
One thing wrong with this is that you need to handle the case where the there is no entry which matches str. Not sure what the specific error is about however as I've (regrettably) done the same dip into a map to retrieve a pointer..
If the map does not contain the key you pass to the find method, then it will return objects.end(). Dereferencing this is a runtime error and will likely cause the error you see. Try instead:
map<std::string,Entity*>::iterator findIt;
findIt = objects.find(str);
if ( findIt != objects.end() )
{
return findIt->second;
}
else
{
// Handle error here
}
As others have pointed out, the name that you are looking up doesn't exist in the map. Everyone is quick to suggest that you check the return value of .find(). I suggest, instead, that you don't call .find(). Here is how I would solve your problem:
Entity* RenderList::getByName(std::string str){
return objects[str];
}
In the code above, looking up a non-existent map entry will create an entry with a NULL pointer value, and return NULL.
You need to add some code somewhere to check for a null pointer before you deference it.