Является ли хорошей практикой использование вектора байтов в качестве исходного хранилища для других типов?
Я начал следовать учебному пособию по ECS на YouTube и никогда не видел, чтобы кто-то выделял новую переменную в vector
из uint8
до.
template<typename Component>
uint32 ECSComponentCreate(Array<uint8>& memory, EntityHandle entity, BaseECSComponent* comp)
{
uint32 index = memory.size();
memory.resize(index+Component::SIZE);
Component* component = new(&memory[index])Component(*(Component*)comp);
component->entity = entity;
return index;
}
(полный код можно найти здесь; Array
здесь #define Array std::vector
)
Чем он отличается от использования вектора указателей, почему он лучше?
1 ответ
Это в основном "распределитель пула". Теперь, когда вы знаете, как это называется, вы можете прочитать о том, почему это делается, но производительность, как правило, является мотивацией.
Все распределения выполняются в одном векторе, и в конце весь вектор может быть освобожден сразу (после уничтожения объектов внутри, что вы можете увидеть в функции освобождения чуть ниже).
Две ошибки мне с помощью vector
из байтов:
- Выравнивание. Хотя вы можете решить это с помощью распределенного распределителя. Если вы не знаете, какие типы хранятся заранее и каковы их требования к выравниванию до времени выполнения, то немного расточительно (но не так много для небольшого количества здоровенных контейнеров) просто используйте максимальное выравнивание для динамического распределения.
- Это большая проблема в моей книге, и это связано с тем, как вектор перераспределяет свой массив, когда вы вставляете в него элементы. Если вы храните нетривиально конструктивные / разрушаемые типы там, это может привести к разрушительным последствиям, копируя их байты в память, не вызывая должным образом необходимые движущие / копирующие ctors и dtors.
Если вы хотите, чтобы ваш ECS-магазин непрерывно сохранял в памяти новые типы компонентов, то я рекомендую гораздо менее опасный подход - абстрагировать контейнер компонентов, например:
struct Components
{
virtual ~Components() {}
};
template <class T>
struct ComponentsT: public Components
{
std::vector<T> components;
};
Когда вы хотите зарегистрировать новый тип компонента, Foo
в систему, вы можете динамически распределять и создавать ComponentsT<Foo>
вставьте это в полиморфный контейнер (контейнер контейнеров), и когда вы хотите добавить Foo
компонент к объекту (и, следовательно, создать его экземпляр и добавить его в список компонентов), получить соответствующий реферат Components*
Базовый указатель / ссылка, уменьшить его (можно использовать dynamic_cast
) чтобы ComponentsT<Foo>*
, затем отодвиньте туда свой экземпляр компонента. Теперь вы получили все свои Foos
хранится непрерывно в памяти, не имея дело с выравниванием и аннулированием и так далее.