使用字节向量作为其他类型的原始存储是一种好习惯吗?

Is it a good practice to use a vector of bytes as raw storage for other types?

我已经开始关注 YouTube 上的 ECS 教程,我之前从未见过有人将新变量分配到 uint8vector 中。

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;
}

(有问题的完整代码可以找到here; Array here is #define Array std::vector

它与使用指针向量有何不同,为什么更好?

这基本上是一个 "pool allocator." 既然您知道它的名字,您就可以阅读它的原因,但性能通常是动力。

所有分配都在一个向量中完成,最后整个向量可以一次释放(在销毁其中的对象之后,您可以在下面的释放函数中看到)。

我使用 vector 字节的两个问题是:

  1. 对齐。尽管您可以使用对齐的分配器来解决这个问题。如果您不知道提前存储了哪些类型以及它们在运行时之前的对齐要求,那么稍微浪费(但对于少数大型容器来说不是那么多)只是为动态分配使用最大对齐。
  2. 这是我书中的一个大陷阱,它与 vector 在向其插入元素时如何重新分配其数组有关。如果您在其中存储非平凡的 constructive/destructible 类型,则可能会通过在内存中复制它们的字节而没有正确调用必要的 move/copy ctors 和 dtors 来破坏破坏。

如果你想让你的 ECS 在内存中连续存储全新的组件类型,那么我推荐的一种危险性小得多的方法是抽象你的组件容器,如下所示:

struct Components
{
     virtual ~Components() {}
};

template <class T>
struct ComponentsT: public Components
{
     std::vector<T> components;
};

当你想向系统注册一个新的组件类型时,Foo,你可以动态分配和实例化ComponentsT<Foo>,将其粘贴在多态容器(容器的容器)中,当你想添加一个 Foo 组件到一个实体(并因此实例化它并将它添加到组件列表)时,获取相关的抽象 Components* 基础 pointer/ref,向下转型(可以使用 dynamic_cast) 到 ComponentsT<Foo>*,然后将您的组件实例推回那里。现在,您将所有 Foos 连续存储在内存中,而无需处理对齐和失效等问题。