This simple program is a minimal version of a problem I was having. I have an unordered set of pointers to objects, and while iterating through that set, some of the objects were supposed to be erased from the set. In my larger program, this was causing it to crash. In this smaller one, it's merely removing one element and then ending the loop.
In the example below, we have a game featuring Units
. When units are idle, the game will make them perform an action. Idle Units
are kept track of in an unordered_set
named idle_units
.
During the game's update loop, it would iterate through idle_units
and have them performing an action. When a Unit
's act()
function is called, it is no longer idle, and therefore removed from idle_units
#include <vector>
#include <memory>
#include <unordered_set>
#include <unordered_map>
#include <cstdlib>
#include <iostream>
// forward declarations
struct Unit;
void set_not_idle(Unit* u_);
struct Unit { // a simple object with unique identifier
Unit() { static int id_ = 0; id = id_++; }
void act() { set_not_idle(this); }
int id;
};
// data
std::vector<std::unique_ptr<Unit>> unit_storage;
std::unordered_set<Unit*> unit_ptrs;
std::unordered_map<int, Unit*> unit_from_id;
std::unordered_set<Unit*> idle_units;
Unit* get_unit_ptr(Unit u) { return unit_from_id[u.id]; }
void set_idle(Unit* u_) {
Unit* u = get_unit_ptr(*u_); // ensure we have pointer to unit in storage
idle_units.insert(u);
}
void set_not_idle(Unit* u_) {
Unit* u = get_unit_ptr(*u_); // ensure we have pointer to unit in storage
idle_units.erase(u);
}
void add_unit(Unit u_) {
unit_storage.push_back(std::make_unique<Unit>(u_));
Unit* u_ptr = unit_storage.back().get();
unit_ptrs.insert(u_ptr);
unit_from_id[u_ptr->id] = u_ptr; // set map from id to pointer in storage
set_idle(u_ptr); // units start as idle
}
void print() {
std::cout << "Units in storage: ";
for (auto a : unit_ptrs) {
std::cout << a->id << " ";
}
std::cout << " Idle units: ";
for (auto it = idle_units.begin(); it != idle_units.end(); ++it) {
std::cout << (*it)->id << " ";
}
std::cout << std::endl;
}
int main() {
srand(25);
std::vector<Unit> units;
// randomly populate our unit_storage with 8 units
for (int i = 0; i < 50; i++) units.push_back(Unit());
for (int i = 0; i < 8; ) {
int idx = rand() % units.size();
if (!get_unit_ptr(units[idx])) {
add_unit(units[idx]);
i++;
}
}
print();
// get all idle units, and have them perform an action
for (auto it = idle_units.begin(); it != idle_units.end(); ++it) {
(*it)->act();
}
print();
return 0;
}
This results in the following output:
Units in storage: 36 2 15 43 18 10 38 11 Idle units: 36 2 15 43 18 10 38 11
Units in storage: 36 2 15 43 18 10 38 11 Idle units: 2 15 43 18 10 38 11
Whereas it should result in no Units
remaining in idle_units
. What is the most elegant solution to this?
While trying to solve this, I tried different methods of iteration, including a for (auto it : idle_units)
loop, or by moving the iterator increment to the body of the loop, but neither of those solutions solved the problem.