Является ли хорошей практикой использование вектора байтов в качестве исходного хранилища для других типов?

Я начал следовать учебному пособию по 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 из байтов:

  1. Выравнивание. Хотя вы можете решить это с помощью распределенного распределителя. Если вы не знаете, какие типы хранятся заранее и каковы их требования к выравниванию до времени выполнения, то немного расточительно (но не так много для небольшого количества здоровенных контейнеров) просто используйте максимальное выравнивание для динамического распределения.
  2. Это большая проблема в моей книге, и это связано с тем, как вектор перераспределяет свой массив, когда вы вставляете в него элементы. Если вы храните нетривиально конструктивные / разрушаемые типы там, это может привести к разрушительным последствиям, копируя их байты в память, не вызывая должным образом необходимые движущие / копирующие 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 хранится непрерывно в памяти, не имея дело с выравниванием и аннулированием и так далее.

Другие вопросы по тегам