简体   繁体   中英

Efficient container for different derived classes in C++

When programming games, I used to store all game Objects in a std::vector with initialized and fixed size. Recently I felt the need for some inheritance among the game object classes.

So lets assume I have like 40 classes derived from my class Enemy. If I want to store objects/instances of those classes in a vector, I only have the option of storing them as vector Enemy* right? So the only thing thats contiguously allocated are the pointers, right? So I still will have a lot of cache misses when those need to be dereferenced, right?

Is there any "best practice" way, of storing derived classes in coniguous allocated memory, so that looping through them takes the minimum amount of time?

Boost just accepted a library for exactly this purpose: poly_collection . In particular, you are looking for base_collection

Internally, it uses a number of vectors, one per (derived) type, while providing an interface close to standard containers.

This article by its author provides some design background and a comparison to other solutions, like a vector of unique_ptr . The advantage is two-fold: first, by not using pointers and dynamic memory allocation per element you have better memory locality, and second, grouping elements of the same type together, you help branch prediction and instruction cache for virtual member functions.

What about this?

struct alignas(...) Base {};
struct Derived1 : Base {};
struct Derived2 : Base {};

int main()
{
    std::vector<Base> v(2);
    new (&v[0]) Derived1();
    new (&v[1]) Derived2();
    return 0;
}

Placement new does the trick. It works with polymorphism. The alignas is to ensure the vector contains objects of the same size. Replace the ... by a number (power of 2) such that instances of both Derived1 and Derived2 fit in the vector. If sizeof(Derived1) returns 16 and sizeof(Derived2) returns 24, you would need a alignas(32) .

EDIT

As @KubaOber stated, alignas is not designed to manage allocation sizes, but only for positioning the object in memory. A better way to achieve your goal is using std::variant . Something like this:

int main()
{
    std::vector<std::variant<Derived1, Derived2>> v;
    v.emplace_back(Derived1());
    v.emplace_back(Derived2());

    for (const auto& e : v)
        std::visit(VisitPackage(), e);

    return 0;
}

where VisitPackage could be something like this:

struct VisitPackage
{
    void operator()(const Derived1&) { std::cout << "Derived 1.\n"; }
    void operator()(const Derived2&) { std::cout << "Derived 2.\n"; }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM