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
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();