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.
Related
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));
I have C++ code like this:
if(rtstructure.find(varName) != rtstructure.end()) {
rtdef = rtstructure[varName];
}
where rtstructure is std::map with std::string for the key.
This code works but it seems like a waste to make it search twice for the same key. If I omit the if case around the assignment, the program crashes if varName points to a key that doesn't exist.
Can I in a single map operation look up a key in a std::map and get its value if it exists, without crashing if it doesn't exist?
find give you a std::map<>::iterator that holds/point to std::pair<>. The iterator can be saved and reused (given that you did not do anything to invalidate it such as erase).
// i don't know the type of rtstructure so i use auto
// you can replace it to the correct type if C++11 is not available
auto it = rtstructure.find(varName);
if(it != rtstructure.end()) {
rtdef = it->second;
}
I'm interested in removing an element with a specific key out of a map and use this element.
Something that will look like:
itr = MyMap.pop(wantedKey);
//Now MyMap is missing the element which has the key 'wantedKey'.
//Do something with this element through 'itr'.
Is there an stl map method for doing this?
EDIT
Following carleeto's response, I want to clarify: What I need is the element being removed from the map and the program being able to use it afterwards, it could be the element itself as a pair, not necessarily an iterator.
There are two options: use it in-place then remove it, or move it to a local variable, remove the entry, then use it.
// use-remove
auto i = MyMap.find(wantedKey);
if (i != MyMap.end()) {
// use-remove
use(i->second);
MyMap.erase(i);
// or
// move-remove-use
auto x = std::move(i->second);
MyMap.erase(i);
use(x);
} else {
// Not found
}
Not that I know of, but you can use std::map::find to get an iterator and then call std::map::erase with said iterator as an argument when you're done.
From your variable naming, I think you might be confusing concepts here.
itr = MyMap.pop(wantedKey);
//Do something with this element through 'itr'.
Iterators only point to elements in containers. Therefore, if you had received an iterator through a function called pop (even if it existed), the iterator would reference not the element you popped, but probably the one after or before it, like std::vector::erase. This is because the purpose of an iterator is to iterate over the elements in a container. Therefore, if an element is not in the container, you cannot get an iterator to it. However, even if you used the iterator returned by the erase function, it would not reference you would be expecting it to.
So you can erase an element from the map, like so many have pointed out, by searching for it, getting the ierator to it and then calling erase with that iterator. but you cannot get an iterator that points to element you have erased. Hope this clears things up.
UPDATE: If all you want is to access the element and use it, then all you need to do use std::map::find to get an iterator and std::map::erase to remove the item from the map, once you have finished using the iterator. The reason is that even if you have stored a copy of the iterator for future use, once you call erase, it will be invalidated. To be able to access it after you have erased it, depending on scope, you will probably need to copy it.
Finally, what you want to do is a very common task - look up a map based on a key and perform an operation on the associated element. It's quite likely that you have a list of keys to go through. You should also look up functors, std::for_each and std::transform. I realise this is not operating on the element after you have removed it, but I thought I would add it in, seeing as how its a related operation. For example: You could move all elements that match a list of keys into another container (say, a vector, and then use the above to operate on them).
Probably what you want to do is
itr = MyMap.find('thing in a string');
to find the iterator and then use it,
MyMap.erase(itr)
And then erase it.
Pop() belongs to the stack datastructure. To access an element of a map, use the [] operator (http://www.cplusplus.com/reference/map/map/operator%5B%5D/), to remove it from the map use (http://www.cplusplus.com/reference/map/map/erase/).
itr = MyMap.find(wantedKey);
if(itr != MyMap.end()) {
use( itr->second );
MyMap.erase(itr);
}
your.uncle = bob;
Using C++'s std::map<T, U>::find():
map.erase(map.find(key));
The way I did it is below. In my case the map stores std::shared_ptr values, making the copy cheap(ish), and the object ownership transferral clear.
auto it = MyMap.find( wantedkey );
if ( it == MyMap.end() ) throw runtime_error("not found");
auto ret = it->second; // make copy of shared_ptr
MyMap.erase(it);
return ret;
The caller gets a shared_ptr with a reference count of at least one (from the copy). Note the function must return the shared_ptr by value, etc.
I have a map of type std::map<std::string, std::vector<MyClass>>. The map is filled in this way that I create a vector and put it with a guid as a pair into the map. Then I want to call a function, give the just inserted vector to it and let it fill the vector. It looks like that:
{
std::string guid = "aGUID"
std::vector<MyClass> vec_myClass(0);
my_map[guid] = vec_myClass;
std::vector<MyClass>& vec_ref = my_map[guid];
FillVector(vec_ref);
}
FillVector(std::vector<MyClass>& vec) { vec.push_back(...); }
I think the [] operator returns a reference of the item in my_map, which I can give to a function to work with it, but my application crashes at this point. I am putting the vector first into the map (when it is empty) because I want to avoid copying effort as function FillVector puts lots of items into the vector. Where is my mistake? Might it be wrong to pass a reference by reference to a function? Or is there a clearly better solution to this? I prefer references over pointers here. Thx, and all the best.
All that code simplifies to:
{
std::string guid = "aGUID"
FillVector(my_map[guid]);
}
Btw. I think your problem does not appear to be here, but in code you don't show us...
std::map operator would create value for the key internally if it does not exist. see this link. passing the reference to function is ok, the problem seem to be somewhere else in your code.
I have a map of objects and I want to update the object mapped to a key, or create a new object and insert into the map. The update is done by a different function that takes a pointer to the object (void update(MyClass *obj))
What is the best way to "insert or update" an element in a map?
The operator[]
With something like the following snippet:
std::map<Key, Value>::iterator i = amap.find(key);
if (i == amap.end())
amap.insert(std::make_pair(key, CreateFunction()));
else
UpdateFunction(&(i->second));
If you want to measure something that might improve performance you might want to use .lower_bound() to find where an entry and use that as a hint to insert in the case where you need to insert a new object.
std::map<Key, Value>::iterator i = amap.lower_bound(key);
if (i == amap.end() || i->first != key)
amap.insert(i, std::make_pair(key, CreateFunction()));
// Might need to check and decrement i.
// Only guaranteed to be amortized constant
// time if insertion is immediately after
// the hint position.
else
UpdateFunction(&(i->second));
something like:
map<int,MyClass*> mymap;
map<int,MyClass*>::iterator it;
MyClass* dummy = new MyClass();
mymap.insert(pair<int,MyClass*>(2,dummy));
it = mymap.find(2);
update(it.second);
here a nice reference link
The operator[] already does, what you want. See the reference for details.
The return value of insert is "a pair consisting of an iterator to the inserted element (or to the element that prevented the insertion) and a bool denoting whether the insertion took place."
Therefore you can simply do
auto result = values.insert({ key, CreateFunction()});
if (!result.second)
UpdateFunction(&(result.first->second));
NOTE:
Since your question involved raw pointers, and you said you wanted your Update function to take a pointer, I have made that assumption in my snippet. Assume that CreateFunction() returns a pointer and UpdateFunction() expects a pointer.
I'd strongly advise against using raw pointers though.
In C++17, function insert_or_assign insert if not existing and update if there.