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<char[48]>
, 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 <typename Parent, typename... Children>
class polymorphic_vector {
private:
template <typename Base, typename... Others>
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 <typename T>
static constexpr size_t get_max_type_size() {
return sizeof(T);
}
template <typename T, typename Arg, typename... Args>
static constexpr size_t get_max_type_size() {
return max(sizeof(T), get_max_type_size<Arg, Args...>());
}
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<Others...>();
char m_data[m_size];
};
public:
wrapper m_wrapper;
};
using pointer_diff_t = int16_t;
std::vector<polymorphic<Parent, Children...>> m_vector;
std::vector<pointer_diff_t> m_pointer_diff;
template <typename BaseAddr, typename ModifiedAddr>
pointer_diff_t get_pointer_diff(BaseAddr base, ModifiedAddr modified) {
char* base_p = reinterpret_cast<char*>(base);
char* modified_p = reinterpret_cast<char*>(modified);
return base_p - modified_p;
}
template <typename BaseAddr, typename ModifiedAddr>
ModifiedAddr get_modified_addr(BaseAddr base, pointer_diff_t diff) {
char* base_p = static_cast<char*>(base);
return reinterpret_cast<ModifiedAddr>(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<char*, Parent*>(
m_vector[index].m_wrapper.m_data, m_pointer_diff[index]);
}
template <typename Q>
void push_back(const Q& q) {
static_assert(sizeof(Q) <= sizeof(polymorphic<Parent, Children...>));
static_assert(std::is_base_of<Parent, Q>::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<Parent*>(
reinterpret_cast<Q*>(m_vector.back().m_wrapper.m_data))));
}
template <typename Q, typename... Args>
void emplace_back(const Args&... args) {
static_assert(sizeof(Q) <= sizeof(polymorphic<Parent, Children...>));
static_assert(std::is_base_of<Parent, Q>::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<Parent*>(
reinterpret_cast<Q*>(m_vector.back().m_wrapper.m_data))));
}
void shuffle() {
std::vector<int> 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<Parent, 48> v;
v.emplace_back<Parent>();
v.emplace_back<Child1>(param1, param2);
v.emplace_back<Child2>(param1);
v.get(0)->method1();
v.get(1)->method1();