Reputation: 95
How would I store massive amount of objects (Like bullets or such changing constantly), and then remove them by their index? I've heard that vector::erase isn't very efficient.
Upvotes: 3
Views: 965
Reputation: 622
(in)Famous Alexandrescu considers main problem with short-living dynamic small object slow new/free operators and recommends use of custom allocator. std::map will use much of such objects (it uses red-black tree internally), so it is a good idea to either use a pool of preallocated objects or std::map with custom allocator (possibly one taken from Alexandresu's Loki library)
Upvotes: 1
Reputation: 254431
vector::erase
isn't very efficient since it needs to move all the following elements to fill the space in order to preserve the order. However, it sounds like you don't need to preserve the order, in which case you can remove an element much more quickly by replacing it with the one at the end, and then dropping the last element:
std::swap(bullets[index_to_remove], bullets.back());
bullets.pop_back();
Upvotes: 0
Reputation: 11251
From my extremely limited secondhand knowledge of game engine programming, dynamic memory allocation is avoided when possible. So an unordered_set
or list
would be a major no for a "massive amount" with frequent removals.
Edit: after reading @James Kanze's answer I realize that @Cameron's suggestion of swapping removed bullets with the last element doesn't work because it changes the indices of live bullets. I guess you'd have to use James's suggestion, or possibly a bit vector to mark dead bullets.
Keeping the bullets compacted in an array would be great for cache performance too. A hash table would cache better than std::map
's red-black tree, but why incur the overhead of hashing if you already refer to the bullets by index?
Upvotes: 0
Reputation: 153909
If you're identifying the bullets by their index in the vector, then erase
isn't a good idea; it will change the index of all of the following elements. The fact that a bullet is identified by its index constrains your choices somewhat. The best solution in that case is probably to simply mark the entry as invalid, and reuse it later, either by keeping a list of invalid indexes (easily done in an std::vector
), or simply by scanning the vector each time you want to create a new bullet. If you have different types of bullets (and you probably will, as the game evolves), you'll need to allocate the bullets dynamically anyway, keeping a pointer in the vector; a null pointer is then a good choice for invalid.
Upvotes: 0
Reputation: 146910
unordered_set<Bullet*>
will do fine. Then you can simply use a Bullet*
everywhere and forget indexing. Best allocated from an object pool or something like that.
Or, since the Standard kindly screwed up the interface, you may need instead std::unordered_map<Bullet*, std::unique_ptr<Bullet>>
for the better exception safety and such.
Upvotes: 1
Reputation: 46607
Use std::map
(or C++11s std::unordered_map
), these containers have better amortized runtime complexity for insertion and erase operations. std::list
is also an option (its the obvious option, but I mentioned the others first since they also allow quick lookup/search which is in many game scenarios even more important).
At a higher level, you should definitely read up on C++ containers and what runtime complexity is in general. A wise choice of container structures is essential to good performance.
Upvotes: 2