I need to implement a huge hash table which supports multiple threads to insert and get at the same time. The keys are int and the second elements are vectors of object T.
class T {
//class definitions here
}
Currently the implementation is helped with tbb::concurrent_unordered_map. On the documentation it seems to allow insertion and traversal at the same time. However, running with multiple pthreads will lead to segmentation fault although sequential test is perfectly fine. Note that there is definitely no erase or pop operations, i.e. the hash table is only allowed to grow.
std::vector<T*> get(int key) {
//Note that the hash table hashTable is shared by multiple threads
tbb::concurrent_unordered_map<int, std::vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
return it->second;
else {
std::vector<T*> newvector;
return newvector;
}
}
void insert(int key, T *t) {
tbb::concurrent_unordered_map<int, std::vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
it->second.push_back(t);
else {
std::vector<T*> newTs;
newTs.push_back(t);
hashTable.insert(it, makepair(key, newTs));
}
}
To debug what happened, I first changed std::vector to tbb::concurrent_vector, and then modify the function get() and insert() as follows.
std::vector<T*> get_test(int key) {
std::vector<T*> test;
//Note that the hash table hashTable is shared by multiple threads
tbb::concurrent_unordered_map<int, tbb::concurrent_vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
test.insert(test.end(), it->second.begin(), it->second.end());
for (T* _t : test)
if (!_t)
printf("Bug happens here!\n"); //Segfault is originated here because a NULL is returned in the vector
return test;
}
void insert_test(int key, T *t) {
//Here t is guaranteed to be not NULL
if(!t)
std::terminate();
tbb::concurrent_unordered_map<int, tbb::concurrent_vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
it->second.push_back(t);
else {
std::vector<T*> newTs;
newTs.push_back(t);
hashTable.insert(it, makepair(key, newTs));
}
}
Now we can see the reason that the parallel program crashes is some NULL pointer is returned in get_test() function. Recall that in insert_test() function NULL is never inserted as the second elements.
The following are the questions to ask.
(1) I read from somewhere that concurrent insertion in tbb::concurrent_unordered_map may cause some of the insertion attempt failed and then the temp objects is destroyed. Is this the reason that NULL is observed in get_test() function?
(2) Can TBB really allow insertion and traversal at the same time? This means while some threads are inserting, the others might call get() at the same time.
(3) Is there any better implementation, i.e. better concurrent hash table that support concurrent insert and read?
As #for_stack suggested, I have verified the second elements are concurrent_vectors and the entire program is runnable. A further test is conducted as follows:
tbb::concurrent_vector<T*> get_test(int key) {
tbb::concurrent_vector<T*> test;
//Note that the hash table hashTable is shared by multiple threads
tbb::concurrent_unordered_map<int, tbb::concurrent_vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
test = it->second;
int i = 0;
for (T* _t : test)
if (!_t)
i++;
if (i > 0)
printf("%d of %lu Ts are NULL\n", i, test.size()); //Segfault is originated here because a NULL is returned in the vector
return test;
}
void insert_test(int key, T *t) {
//Here t is guaranteed to be not NULL
if(!t)
std::terminate();
tbb::concurrent_unordered_map<int, tbb::concurrent_vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
it->second.push_back(t);
else {
tbb::concurrent_vector<T*> newTs;
newTs.push_back(t);
hashTable.insert(it, make_pair(key, newTs));
}
}
The output is
1 of 2 Ts are NULL
This means not all objects (T) returned in get() are NULL.
Again the sequential (or even 1 thread) running is fine.
TBB CAN support concurrent insertion and traversal for concurrent_xxx containers. However, your original code has race conditions:
std::vector<T*> get(int key) {
// other code
return it->second; # race condition 1
// other code
}
The get function try to return a copy of vector<T*> (read), while other threads might call insert to modify the vector<T*> (write).
void insert(int key, T *t) {
// other code
it->second.push_back(t); # race condition 2
// other code
}
The insert function try to modify the vector<T*> with no lock guard. If there are several threads call insert at the same time (multiple write), OOPS!
concurrent_unordered_map only has safe guarantee for container operations, while it has no guarantee for operations on the mapped_value. You have to do it yourself.
Just as what you've tried, you can replace the vector<T*> with concurrent_vector<T*>. However, the new code you posted doesn't compile, you have to modify the implementation of insert_test:
void insert_test(int key, T *t) {
//Here t is guaranteed to be not NULL
if(!t)
std::terminate();
tbb::concurrent_unordered_map<int, tbb::concurrent_vector<T*>>::iterator it = hashTable.find(key);
if (it != hashTable.end())
it->second.push_back(t);
else {
// std::vector<T*> newTs; # this is wrong!
tbb::concurrent_vector<T*> newTs;
newTs.push_back(t);
hashTable.insert(it, make_pair(key, newTs)); // it should be make_pair not makepair
}
}
"TBB CAN support concurrent insertion and traversal for concurrent_xxx containers." - not exactly. Traversal is a tricky thing when there is no memory reclamation support like in TBB and concurrent erasure is supported by a container (concurrent_hash_map). However, concurrent_unordered_map does not support thread-safe erase() and thus thread-safe traversal is supported.
#Anton my friend, the concurrent_unordered containers do support concurrent traversal and insertion; they're implemented as skip-lists. In the non-multi case the result of the pointer swing is tested, and if it fails the search is started again from the point of insertion.
Now C++ may have changed in the last few weeks since I worked at Intel, but I think there are serious bugs in the original code:
if (it != hashTable.end())
return it->second; // return a copy???
else {
std::vector<T*> newvector; // this is stack-allocated
return newvector; // return a copy??
}
The return value is vector, not reference or pointer to vector, so you are going to get copies of the current contents as return values, and inserting into the copy will not change any vector that is in the set. Maybe fix that, and make sure there is no asynchronous reference to a vector, and then look for remaining bugs.
Related
push_back, begin, end are described as concurrent safe in
https://learn.microsoft.com/en-us/cpp/parallel/concrt/reference/concurrent-vector-class?view=vs-2019#push_back
However the below code is asserting. Probably because element is added but not initialized yet.
struct MyData
{
explicit MyData()
{
memset(arr, 0xA5, sizeof arr);
}
std::uint8_t arr[1024];
};
struct MyVec
{
concurrency::concurrent_vector<MyData> v;
};
auto vector_pushback(MyVec &vec) -> void
{
vec.v.push_back(MyData{});
}
auto vector_loop(MyVec &vec) -> void
{
MyData myData;
for (auto it = vec.v.begin(); it != vec.v.end(); ++it)
{
auto res = memcmp(&(it->arr), &(myData.arr), sizeof myData.arr);
assert(res == 0);
}
}
int main()
{
auto vec = MyVec{};
auto th_vec = std::vector<std::thread>{};
for (int i = 0; i < 1000; ++i)
{
th_vec.emplace_back(vector_pushback, std::ref(vec));
th_vec.emplace_back(vector_loop, std::ref(vec));
}
for(auto &th : th_vec)
th.join();
return 0;
}
According to the docs, it should be safe to append to a concurrency::concurrent_vector while iterating over it because the elements are not actually stored contiguously in memory like std::vector:
A concurrent_vector object does not relocate its elements when you append to it or resize it. This enables existing pointers and iterators to remain valid during concurrent operations.
However, looking at the actual implementation of push_back in VS2017, I see the following, which I don't think is thread-safe:
iterator push_back( _Ty &&_Item )
{
size_type _K;
void *_Ptr = _Internal_push_back(sizeof(_Ty), _K);
new (_Ptr) _Ty( std::move(_Item));
return iterator(*this, _K, _Ptr);
}
I have to speculate on _Internal_push_back here, but I'd wager it allocates raw memory for storing the item (and points the last element towards this new node) so that the next line can use emplacement new. I'd imagine that _Internal_push_backis internally thread-safe, however I don't see any synchronization happening before the emplacement new. Meaning the following is possible:
memory is obtained and the node is "present" (yet emplacement new hasn't happend)
the looping thread encounters this node and performs memcmp to discover that they're not equal
emplacement new happens.
There's definitely a race condition here. I can spontaneously reproduce the problem, moreso the more threads I use.
I recommend that you open a ticket with Microsoft support on this one.
How would i find a element in a vector from one of its arguments set with emplace_back
Trying to detach thread then delete it from vector.
std::vector<std::thread> vTimerThreads;
void SetTimer(UINT ID, DWORD dwMilliseconds)
{
// timerThreadProc is my thread that handles my timers
vTimerThreads.emplace_back(timerThreadProc, ID, dwMilliseconds);
}
void DeleteTimer(UINT ID)
{
//Find thread by ID?
// thread.detach();
// then delete
}
SetTimer(TIMER1, 5000);
std::find_if sounds like what you want if you're just going to remove based on id.
void DeleteTimer(std::thread::id ID)
{
std::vector<std::thread>::iterator itr = std::find_if(vTimerThreads.begin(), vTimerThreads.end(), [&](const std::thread& t) { return t.get_id() == ID; });
if( itr != vTimerThreads.end() )
vTimerThreads.erase(itr);
}
I've used a lambda here but it's not necessary.
If you're thinking of using a large number of threads, maybe a different data structure would suit you better. Have you considered an std::set for faster searching? Perhaps even a map or hash_map would be good for you, where the id is the key? You could put the threads into these containers with move semantics instead of emplace_back without having copying (as I suspect is motivating you to use emplace).
Check out the std::algorithm library though, there's some great stuff in there
EDIT:
I see in one of the comments OP says that ID is not in fact the thread id. Unless we can get clarification on what member of T for std::vector<T> we are meant to be searching on, an explicit solution cannot be provided.
As long as I'm doing an edit, here's some code for adding threads to a std::map without copying. With the following code it'll be trivial to find an element by std::thread::id or whatever else you want to use as a key and then delete it.
std::map<std::thread::id, std::thread> mapTimerThreads;
void AddNewThreadToMap()
{
std::thread t;
mapTimerThreads[t.get_id()] = std::move(t);
}
If you want to do a simple linear search (which makes sense if the number of threads is not large) you can just do
void DeleteTimer(UINT ID)
{
for(int i = 0; i < vTimerThreads.size(); i++)
if(vTimerThreads[i].get_id() == ID)
{
vTimerThreads.erase(vTimerThreads.begin()+i);
break;
}
}
If your number of threads is large, arbitrary deletion like this is expensive - you might want to consider something like forward_list instead of vector in that case.
In my application I need the ability to traverse a doubly linked list starting from any arbitrary member of the list and continuing past the end(), wrapping around to the begin() and continue until the traversal reaches where it started.
I decided to use std::list for the underlying data structure and wrote a circulate routine to achieve this. However it's showing certain unexpected behavior when it's wrapping around from end() to begin(). Here's my implementation
template <class Container, class BiDirIterator>
void circulate(Container container, BiDirIterator cursor,
std::function<void(BiDirIterator current)> processor)
{
BiDirIterator start = cursor;
do {
processor(cursor);
cursor++;
if (cursor == container.end()) {
cursor = container.begin(); // [A]
}
} while (cursor != start);
}
// ...
typedef int T;
typedef std::list<T> TList;
typedef TList::iterator TIter;
int count = 0;
TList l;
l.push_back(42);
circulate<TList, TIter>(
l, l.begin(),
[&](TIter cur) {
std::cout << *cur << std::endl;
count++;
}
);
The output is:
42
-842150451
When I step through the code I see that the line marked [A] is never reached. The cursor is never equal to container.end(). Surprisingly, invoking ++ on that cursor, takes it to container.begin() in next pass automatically. (I suppose that's specific to this STL implementation).
How can I fix this behavior?
The issue here is that you are taking Container by value. This causes a copy so the iterators returned by container.end() and container.begin() are not that same as the iterator passed to the function. Instead if you pass Container by reference then the code works correctly.
Live Example
I have a vector of object pointers that I am adding to and deleting from while looping through to update objects. I can't seem to delete objects that have "died" from the vector without causing a memory error. I'm not really sure what I'm doing wrong. Listed below is my update method and it's sub method.
void Engine::update(string command){
if(getGameOver()==false){
for(p=objects.begin();p!=objects.end();p++){
spawnUpdate(command);
//cout<<"Spawn"<<endl;
objectUpdate(command);
//cout<<"object"<<endl;
scrollUpdate(command);
// cout<<"scroll"<<endl;
killUpdate(command);
//cout<<"kill"<<endl;
}
}
}
void Engine::killUpdate(std::string command){
if((*p)->getIsDead()==true){delete *p;}
}
void Engine::objectUpdate(string command){
(*p)->update(command,getNumObjects(),getObjects());
if(((*p)->getType() == PLAYER)&&((*p)->getPosX()>=getFinishLine())){setGameOver(true);}
}
void Engine::scrollUpdate(string command){
//Check player position relative to finishLine
if(((*p)->getType() == PLAYER)&&((*p)->getPosX()>(SCREEN_WIDTH/2))){
(*p)->setPosX((*p)->getPosX()-RUN_SPEED);
setFinishLine(getFinishLine()-RUN_SPEED);
for(q=objects.begin();q!=objects.end();q++){
//Shift objects to pan the screen
if((*q)->getType() == OPPONENT){(*q)->setPosX((*q)->getPosX()-RUN_SPEED);}
if((*q)->getType() == BLOCK){(*q)->setPosX((*q)->getPosX()-RUN_SPEED);}
}
}
}
void Engine::spawnUpdate(string command){
if(command.compare("shoot")==0){
cout<<"Bang!"<<endl;
if((*p)->getType() == PLAYER){objects.push_back(new Bullet((*p)->getPosX(),(*p)->getPosY(),(*p)->getState()));cout<<"Bullet success "<<endl;}
}
}
Some assumptions/definitions:
objects a member variable, something like vector<Object*> objects;
p is also a member variable, something like vector<Object*>::iterator p;
So p is an iterator, *p is an Object pointer, and **p is an Object.
The problem is that this method:
void Engine::killUpdate(std::string command) {
if ((*p)->getIsDead() == true) {
delete *p;
}
}
deallocates the Object pointed to by *p, the pointer in the vector at the position referenced by the p iterator. However the pointer *p itself is still in the vector, now it just points to memory that is no longer allocated. Next time you try to use this pointer, you will cause undefined behavior and very likely crash.
So you need to remove this pointer from your vector once you have deleted the object that it points to. This could be as simple as:
void Engine::killUpdate(std::string command) {
if ((*p)->getIsDead() == true) {
delete *p;
objects.erase(p);
}
}
However, you are calling killUpdate from update in a loop that iterates over the objects vector. If you use the code above, you will have another problem: once you erase p from the objects vector, it is no longer safe to execute p++ in your for-loop statement, because p is no longer a valid iterator.
Fortunately, STL provides a very nice way around this. vector::erase returns the next valid iterator after the one you erased! So you can have the killUpdate method update p instead of your for-loop statement, e.g.
void Engine::update(string command) {
if (getGameOver() == false) {
for (p = objects.begin(); p != objects.end(); /* NOTHING HERE */) {
// ...
killUpdate(command);
}
}
}
void Engine::killUpdate(std::string command) {
if ((*p)->getIsDead() == true) {
delete *p;
p = objects.erase(p);
} else {
p++;
}
}
This is of course assuming that you always call killUpdate in the loop, but I'm sure you can see the way around this if you don't -- just execute p++ at the end of the for-loop body in the case that you haven't called killUpdate.
Also note that this is not particularly efficient, since every time you erase an element of the vector, the elements that follow it have to be shifted back to fill in the empty space. So this will be slow if your objects vector is large. If you used a std::list instead (or if you are already using that), then this is not a problem, but lists have other drawbacks.
A secondary approach is to overwrite each pointer to a deleted object with nullptr and then use std::remove_if to remove them all in one go at the end of the loop. E.g.:
void Engine::update(string command) {
if (getGameOver() == false) {
for (p = objects.begin(); p != objects.end(); p++) {
// ...
killUpdate(command);
}
}
std::erase(std::remove_if(objects.begin(), objects.end(),
[](const Object* o) { return o == nullptr; }),
objects.end());
}
void Engine::killUpdate(std::string command) {
if ((*p)->getIsDead() == true) {
delete *p;
*p = nullptr;
}
}
The assumption this time is that you will never have a nullptr element of objects that you want to keep for some reason.
Since you seem to be a beginner, I should note that this:
std::erase(std::remove_if(objects.begin(), objects.end(),
[](const Object* o) { return o == nullptr; }),
objects.end());
is the erase-remove idiom, which is explained well on Wikipedia. It erases elements from the vector if they return true when a given function object is called on them. In this case, the function object is:
[](const Object* o) { return o == nullptr; }
Which is a lambda expression and is essentially shorthand for an instance of an object with this type:
class IsNull {
public:
bool operator() (const Object* o) const {
return o == nullptr;
}
};
One last caveat to the second approach, I just noticed that you have another loop over objects in scrollUpdate. If you choose the second approach, be sure to update this loop to check for nullptrs in objects and skip them.
Here is an issue (formatted for readability):
void Engine::update(string command)
{
if (getGameOver()==false)
{
for (p=objects.begin();p!=objects.end();p++)
{
spawnUpdate(command); // changes vector
//...
}
}
//...
}
void Engine::spawnUpdate(string command)
{
//...
objects.push_back(new Bullet((*p)->getPosX(),(*p)->getPosY(),(*p)->getState())); // no
//...
}
You have a loop with iterator p that points to elements in the object vector. When you call objects.push_back, the iterator for the vector may become invalidated. Thus that loop iterator p is no longer any good. Incrementing it in the for() will cause undefined behavior.
One way to get around this is to create a temporary vector that holds your updates. Then you add the updates at the end of your processing:
void Engine::update(string command)
{
std::vector<Object*> subVector;
if (getGameOver()==false)
{
for (p=objects.begin();p!=objects.end();p++)
{
spawnUpdate(command, subVector);
//...
}
}
// add elements to the vector
object.insert(object.end(), subVector.begin(), subVector.end());
}
void Engine::spawnUpdate(string command, std::vector<Object*>& subV)
{
if (command.compare("shoot")==0)
{
cout<<"Bang!"<<endl;
if ((*p)->getType() == PLAYER)
subV.push_back(new Bullet((*p)->getPosX(),(*p)->getPosY(),(*p)->getState()));
cout<<"Bullet success "<<endl;
}
}
You could avoid most of these issues by not using raw pointers. Clearly your code uses the semantic that the vector owns the pointers, so you can express this directly:
std::vector< std::unique_ptr<Object> > objects;
Then you may insert into the vector by using objects.emplace_back(arguments,to,Object,constructor); , and when you remove from the vector it will automatically delete the Object.
You still need to watch out for erase invalidating iterators, so keep using the erase-remove idiom as explained by Tyler McHenry. For example:
objects.erase( std::remove_if( begin(objects), end(objects),
[](auto&& o) { return o->getIsDead(); }), end(objects) );
Note - auto&& is permitted here since C++14; in C++11 you'd have to use std::unique_ptr<Object>&. Required includes are <algorithm> and <memory>.
And please stop using global iterators, keep p local to the function and pass any arguments you need to pass.
I couldn't find an instance of how to do this, so I was hoping someone could help me out. I have a map defined in a class as follows:
std::map<std::string, TranslationFinished> translationEvents;
TranslationFinished is a boost::function. I have a method as part of my class that iterates through this map, calling each of the functions like so:
void BaseSprite::DispatchTranslationEvents()
{
for(auto it = translationEvents.begin(); it != translationEvents.end(); ++it)
{
it->second(this);
}
}
However it's possible for a function called by it->second(this); to remove an element from the translationEvents map (usually itself) using the following function:
bool BaseSprite::RemoveTranslationEvent(const std::string &index)
{
bool removed = false;
auto it = translationEvents.find(index);
if (it != translationEvents.end())
{
translationEvents.erase(it);
removed = true;
}
return removed;
}
doing this causes a debug assertion fail when the DispatchTranslationEvents() tries to increment the iterator. Is there a way to iterate through a map safely with the possibility that a function call during the iteration may remove an element from the map?
Thanks in advance
EDIT: Accidently C/Pd the wrong Remove Event code. Fixed now.
map::erase invalidates the iterator being deleted (obviously), but not the rest of the map.
This means that:
if you delete any element other than the current one, you're safe, and
if you delete the current element, you must first get the next iterator, so you can continue iterating from that (that's why the erase function for most containers return the next iterator). std::map's doesn't, so you have to do this manually)
Assuming you only ever delete the current element, then you could simply rewrite the loop like this:
for(auto it = translationEvents.begin(); it != translationEvents.end();)
{
auto next = it;
++next; // get the next element
it->second(this); // process (and maybe delete) the current element
it = next; // skip to the next element
}
Otherwise (if the function may delete any element) it may get a bit more complicated.
Generally speaking it is frowned upon to modify the collection during iteration. Many collections invalidate the iterator when the collection is modified, including many of the containers in C# (I know you're in C++). You can create a vector of events you want removed during the iteration and then remove them afterwards.
After reading all other answers, I am at an advantage here... But here it goes.
However it's possible for a function called by it->second(this); to remove an element from the translationEvents map (usually itself)
If this is true, that is, a callback can remove any element from the container, you cannot possibly resolve this issue from the loop itself.
Deleting the current callback
In the simpler case where the callback can only remove itself, you can use different approaches:
// [1] Let the callback actually remove itself
for ( iterator it = next = m.begin(); it != m.end(); it = next ) {
++next;
it->second(this);
}
// [2] Have the callback tell us whether we should remove it
for ( iterator it = m.begin(); it != m.end(); ) {
if ( !it->second(this) ) { // false means "remove me"
m.erase( it++ );
} else {
++it;
}
}
Among these two options, I would clearly prefer [2], as you are decoupling the callback from the implementation of the handler. That is, the callback in [2] knows nothing at all about the container in which it is held. [1] has a higher coupling (the callback knows about the container) and is harder to reason about as the container is changed from multiple places in code. Some time later you might even look back at the code, think that it is a weird loop (not remembering that the callback removes itself) and refactor it into something more sensible as for ( auto it = m.begin(), end = m.end(); it != end; ++it ) it->second(this);
Deleting other callbacks
For the more complex problem of can remove any other callback, it all depends on the compromises that you can make. In the simple case, where it only removes other callbacks after the complete iteration, you can provide a separate member function that will keep the elements to remove, and then remove them all at once after the loop completes:
void removeElement( std::string const & name ) {
to_remove.push_back(name);
}
...
for ( iterator it = m.begin(); it != m.end(); ++it ) {
it->second( this ); // callback will possibly add the element to remove
}
// actually remove
for ( auto it = to_remove.begin(); it != to_begin.end(); ++it ) {
m.erase( *it );
}
If removal of the elements need to be immediate (i.e. they should not be called even in this iteration if they have not yet been called), then you can modify that approach by checking whether it was marked for deletion before executing the call. The mark can be done in two ways, the generic of which would be changing the value type in the container to be a pair<bool,T>, where the bool indicates whether it is alive or not. If, as in this case, the contained object can be changed you could just do that:
void removeElement( std::string const & name ) {
auto it = m.find( name ); // add error checking...
it->second = TranslationFinished(); // empty functor
}
...
for ( auto it = m.begin(); it != m.end(); ++it ) {
if ( !it->second.empty() )
it->second(this);
}
for ( auto it = m.begin(); it != m.end(); ) { // [3]
if ( it->second.empty() )
m.erase( it++ );
else
++it;
}
Note that since a callback can remove any element in the container, you cannot erase as you go, as the current callback could remove an already visited iterator. Then again, you might not care about leaving the empty functors for a while, so it might be ok just to ignore it and perform the erase as you go. Elements already visited that are marked for removal will be cleared in the next pass.
My solution is to first create a temporary container, and swap it with the original one. Then you can iterator through the temporary container and insert the ones you want to keep to the original container.
void BaseSprite::DispatchTranslationEvents()
{
typedef std::map<std::string, TranslationFinished> container_t;
container_t tempEvents;
tempEvents.swap(translationEvents);
for(auto it = tempEvents.begin(); it != tempEvents.end(); ++it)
{
if (true == it->second(this))
translationEvents.insert(it);
}
}
And the TranslationFinished functions should return true if it want to be keeped and return false to get removed.
bool BaseSprite::RemoveTranslationEvent(const std::string &index)
{
bool keep = false;
return keep;
}
There should be a way for you to erase a element during your iteration, maybe a little tricky.
for(auto it = translationEvents.begin(); it != translationEvents.end();)
{
//remove the "erase" logic from second call
it->second(this);
//do erase and increase the iterator here, NOTE: ++ action is very important
translationEvents.erase(it++);
}
The iterator will be invalid once the element is removed, so you can not use that iterator to do increase action anymore after you remove it. However, remove an element will not affect other element in map implementation, IIRC. So suffix ++ will copy the iter first and increase the iterator right after that, then return the copy value, which means iterator is increased before erase action, this should be safe for you requirement.
You could defer the removal until the dispatch loop:
typedef boost::function< some stuff > TranslationFunc;
bool BaseSprite::RemoveTranslationEvent(const std::string &index)
{
bool removed = false;
auto it = translationEvents.find(index);
if (it != translationEvents.end())
{
it->second = TranslationFunc(); // a null function indicates invalid event for later
removed = true;
}
return removed;
}
protect against invoking an invalid event in the loop itself, and cleanup any "removed" events:
void BaseSprite::DispatchTranslationEvents()
{
for(auto it = translationEvents.begin(); it != translationEvents.end();)
{
// here we invoke the event if it exists
if(!it->second.empty())
{
it->second(this);
}
// if the event reset itself in the map, then we can cleanup
if(it->second.empty())
{
translationEvents.erase(it++); // post increment saves hassles
}
else
{
++it;
}
}
}
one obvious caveat is if an event is iterated over, and then later on deleted, it will not get a chance to be iterated over again to be deleted during the current dispatch loop.
this means the actual deletion of that event will be deferred until the next time the dispatch loop is run.
The problem is ++it follows the possible erasure. Would this work for you?
void BaseSprite::DispatchTranslationEvents()
{
for(auto it = translationEvents.begin(), next = it;
it != translationEvents.end(); it = next)
{
next=it;
++next;
it->second(this);
}
}