Objects of different classes in a single vector?

前端 未结 7 2283
情书的邮戳
情书的邮戳 2020-12-09 22:58

In my code, I have a set of objects:

class Sphere { ...
class Plane { ...
...

And I need to use a collection of them (they will all have di

7条回答
  •  予麋鹿
    予麋鹿 (楼主)
    2020-12-09 23:58

    Using std::variant would be the best solution if you are using C++17. If not, let me explain:

    Vector of values is in principle faster than vector of pointers because of smaller cache misses. I investigated this solution and this is the basic idea. Imagine you have three types Parent, Child1 and Child2. Size of them are for instance 32 bytes, 40 bytes and 48 bytes. If you create std::vector, in principle you will be able to hold any of the values in it. And since Child1 and Child2 inherit from Base, you can access them through Base* and take advantage of the vtable already present in each class to polymorphically call methods in Child1 and Child2.

    I created a wrapper for std::vector to do exactly this. Here it is

    template 
    class polymorphic_vector {
       private:
        template 
        class alignas(16) polymorphic {
           private:
            static constexpr size_t round_to_closest_16(size_t size) {
                return ((size % 16) == 0) ? size : ((size / 16) + 1) * 16;
            }
            template 
            static constexpr size_t get_max_type_size() {
                return sizeof(T);
            }
    
            template 
            static constexpr size_t get_max_type_size() {
                return max(sizeof(T), get_max_type_size());
            }
    
            static constexpr size_t max(size_t v1, size_t v2) {
                return v1 > v2 ? v1 : v2;
            }
    
            class wrapper {
               public:
                static constexpr int m_size = get_max_type_size();
                char m_data[m_size];
            };
    
           public:
            wrapper m_wrapper;
        };
    
        using pointer_diff_t = int16_t;
    
        std::vector> m_vector;
        std::vector m_pointer_diff;
    
        template 
        pointer_diff_t get_pointer_diff(BaseAddr base, ModifiedAddr modified) {
            char* base_p = reinterpret_cast(base);
            char* modified_p = reinterpret_cast(modified);
            return base_p - modified_p;
        }
    
        template 
        ModifiedAddr get_modified_addr(BaseAddr base, pointer_diff_t diff) {
            char* base_p = static_cast(base);
            return reinterpret_cast(base_p - diff);
        }
    
       public:
        polymorphic_vector(int size) : m_vector(size), m_pointer_diff(size) {}
        polymorphic_vector() : m_vector(), m_pointer_diff() {}
    
        Parent* get(int index) {
            return get_modified_addr(
                m_vector[index].m_wrapper.m_data, m_pointer_diff[index]);
        }
    
        template 
        void push_back(const Q& q) {
            static_assert(sizeof(Q) <= sizeof(polymorphic));
            static_assert(std::is_base_of::value);
    
            m_vector.emplace_back();
            ::new (m_vector.back().m_wrapper.m_data) Q(q);
            m_pointer_diff.emplace_back(get_pointer_diff(
                m_vector.back().m_wrapper.m_data,
                static_cast(
                    reinterpret_cast(m_vector.back().m_wrapper.m_data))));
        }
    
        template 
        void emplace_back(const Args&... args) {
            static_assert(sizeof(Q) <= sizeof(polymorphic));
            static_assert(std::is_base_of::value);
    
            m_vector.emplace_back();
            ::new (m_vector.back().m_wrapper.m_data) Q(args...);
            m_pointer_diff.emplace_back(get_pointer_diff(
                m_vector.back().m_wrapper.m_data,
                static_cast(
                    reinterpret_cast(m_vector.back().m_wrapper.m_data))));
        }
    
        void shuffle() {
            std::vector indexes(m_vector.size());
    
            std::iota(indexes.begin(), indexes.end(), 0);
    
            for (int i = 0; i < m_vector.size(); i++) {
                std::swap(m_pointer_diff[i], m_pointer_diff[indexes[i]]);
                std::swap(m_vector[i], m_vector[indexes[i]]);
            }
        }
    
        void reserve(int size) { m_vector.reserve(size); }
    };
    

    To use it, you need to specify as a template parameter the base class and the size of the internal chunk of memory that would be enough to fit any of the classes you plan to put inside.

    Here is an example for Parent, Child1 and Child2:

    std::vector v;
    
    v.emplace_back();
    v.emplace_back(param1, param2);
    v.emplace_back(param1);
    
    v.get(0)->method1();
    v.get(1)->method1();
    

提交回复
热议问题