Avoiding iterator invalidation using indices, maintaining clean interface

前端 未结 4 703
悲&欢浪女
悲&欢浪女 2021-02-02 02:24

I have created a MemoryManager class which is basically a wrapper around two vectors of pointers that manage lifetime of heap-allocated objects.

O

4条回答
  •  花落未央
    2021-02-02 03:25

    One of the simplest ways to get stable iterators (and references) is to use std::list. And unless you are needing T to be a pointer to a polymorphic base class, it is better to use std::list, as opposed to std::list>.

    If on the other hand, your Entity is a polymorphic base, then consider using std::vector>. Although you can not depend upon iterators remaining valid, you can depend upon pointers and references to Entity remaining valid with std::vector>.

    In your game() example, you never take advantage of stable iterators or pointers. You could just as easily (and more simply) do:

    void game() {
        std::vector mm;
    
        while(gameLoop) {
            mm.erase(std::remove_if(mm.begin(), mm.end(), [](const Entity& e)
                                                          { return e.alive; }),
                                                          mm.end());
            for(auto e : mm) processEntity(e);
            mm.push_back(create());
            auto& newEntity = mm.back();
            // do something with newEntity
        }
    }
    

    During the processEntity loop, there is no way to invalidate iterators. If you did, you had better not use the range-based-for as the end iterator is only evaluated once, at the beginning of the loop.

    But if you really do need stable iterators/references, substituting in std::list would be very easy. I would change the erase/remove to use list's member remove_if instead. It will be more efficient.

    If you do this, and performance testing (not guessing) indicates you've suffered a performance hit over your existing MemoryManager, you can optimize list by using a "stack allocator" such as the one demonstrated here:

    http://howardhinnant.github.io/stack_alloc.html

    This allows you to preallocate space (could be on the stack, could be on the heap), and have your container allocate from that. This will be both high performance and cache-friendly until the pre-allocated space is exhausted. And you've still got your iterator/pointer/reference stability.

    In summary:

    1. Find out / tell us if unique_ptr is actually necessary because Entity is a base class. Prefer container over container>.

    2. Do you actually need iterator/pointer/reference stability? Your sample code does not. If you don't actually need it, don't pay for it. Use vector (or vector> if you must).

    3. If you actually need container>, can you get away with pointer/reference stability while sacrificing iterator stability? If yes, vector> is the way to go.

    4. If you actually need iterator stability, strongly consider using std::list.

    5. If you use std::list and discover via testing it has performance problems, optimize it with an allocator tuned to your needs.

    6. If all of the above fails, then start designing your own data structure. If you get this far, know that this is the most difficult route, and everything will need to be backed up by both correctness and performance tests.

提交回复
热议问题