Should I use an std::vector or a std::unique_ptr here?

huangapple go评论57阅读模式
英文:

Should I use an std::vector or a std::unique_ptr here?

问题

我有一个名为Mesh的类,我希望它对于多种不同的“类似数组”的结构(如verticesindicesvertex_normals等)具有所有权。

我希望Mesh类与这些数据格式的来源完全独立,因为我需要支持许多3D格式。因此,我目前有一组加载函数,用于从特定格式中读取3D数据,创建一个网格,并将其分配给Model的实例(其中包含对特定Meshunique_ptr)。

我目前是通过在加载函数内部简单地堆分配数组,然后将这些数组的指针传递给Mesh构造函数来实现的。但似乎使用std::vectorstd::unique_ptr可能会更安全。一个快速的模拟一个加载函数(特别是用于从OBJ文件加载的加载函数)可能如下所示:

void load_OBJ(std::shared_ptr<Model> model, std::string file_path){
    // 读取3D数据
    ...

    // 创建Mesh对象
    std::unique_ptr<Mesh> mesh(new Mesh());

    // 将3D数据添加到Mesh(???)
    mesh->set_vertices( ??? );
    mesh->set_indices( ??? );
    ...

    // 将Mesh对象分配给Model:
    model->set_mesh(mesh);
};

我的第一直觉是,因为Mesh将对其所有成员拥有独占所有权,所以应该使用std::unique_ptr,就像这样:

class Mesh {
    public:
        std::unique_ptr<Vector3[]> vertices;
        uint32_t num_vertices;

        Mesh() { };
        ~Mesh() { };

        void set_vertices(std::unique_ptr<Vector3[]>& new_vertices, uint32_t num_vertices){
            this->vertices = std::move(new_vertices);
            this->num_vertices = num_vertices;
        };

但后来我开始思考,也许std::vector是更好的选择。在这种情况下,从Mesh中访问数据的速度将非常重要。我的初步基准测试(使用编译器优化)似乎表明,从std::unique_ptr<Vector3[]>std::vector<Vector3>中访问数据几乎无法区分。因此,我不确定是否还有其他方面需要考虑,我是否遗漏了什么?

英文:

I have a Mesh class that I want to have ownership over several different "array-like" structures for vertices,indices,vertex_normals,etc.

I would like for the Mesh class to be totally independent of the source of these formats, as I have many 3d formats I need to support. As such I currently have a set of loader functions that read the 3d data from a specific format, create a mesh, and assign it to an instance of a Model (which contains a unique_ptr to a specific Mesh).

I'm currently doing this by simply heap allocating arrays inside the loader function, then passing the pointers to those arrays into the Mesh constructor. But it seems like using either std::vector or std::unique_ptr would be a safer option. A quick mockup of what one of the loader functions (specifically one meant to load from an OBJ file) might look like is as follows:

void load_OBJ(std::shared_ptr&lt;Model&gt; model, std::string file_path){
    # Read the 3d data
    ...

    # Create the Mesh object
    std::unique_ptr&lt;Mesh&gt; mesh(new Mesh());

    # Add the 3d data to the Mesh (???)
    mesh-&gt;set_vertices( ??? );
    mesh-&gt;set_indices( ??? );
    ...

    # Assign the Mesh object to the Model:
    model-&gt;set_mesh(mesh);
};

My first gut reaction was, because the Mesh will maintain exclusive ownership over all of its members, that I should use std::unique_ptr, so something like this:

class Mesh {
    public:
        std::unique_ptr&lt;Vector3[]&gt; vertices;
        uint32_t num_vertices;

        Mesh() { };
        ~Mesh() { };

        void set_vertices(std::unique_ptr&lt;Vector3[]&gt;&amp; new_vertices, uint32_t num_vertices){
            this-&gt;vertices = std::move(new_vertices);
            this-&gt;num_vertices = num_vertices;
        };

But then I started to wonder if perhaps std::vector is a better way to go. In this context, the speed of being able to access the data from Mesh is going to be extremely important. My rough benchmarks (with compiler optimizations) hint to me though that accessing data from either std::unique_ptr&lt;Vector3[]&gt; and std::vector&lt;Vector3&gt; are virtually indistinguishable. So I'm not sure if there is anything else to take into consideration here that I am missing?

答案1

得分: 3

std::vector 给您提供了对一个动态大小的元素数组的独占所有权,这正是您想要的。它还通过允许您省略容易出错的显式大小变量来简化事情。我会建议使用它。

使用向量也不会引入太多开销:它只是一个指向一些带有sizecapacity的内存的指针。 (在实践中,这些也被实现为指针,所以一个向量只是三个指针。)

如果您要将元素push_back到向量中,请确保首先使用适当的大小调用reserve

vertices.reserve(numVertices);
for (size_t i = 0; i < numVertices; ++i) {
    // 载入顶点
    vertices.push_back(vertex);
}

否则,向量的默认增长策略可能使它变得比您需要的要大,浪费内存。 (如果我记得正确,向量的分配存储变满时默认会翻倍大小。)

如果您想要将数据memcpy到向量中,您必须首先调整其大小以分配适当数量的存储空间。

要在不复制的情况下传递向量,请使用std::move。 这将归结为与您的unique_ptr相同的操作 - 只是将一些指针移动了一下。 您可以在需要时轻松复制向量,但没有必要。

如果您想要创建一个“消耗”向量(接管它的所有权)的函数或构造函数,请将其按值传递并将其移动到其最终位置:

void Model::setMesh(std::vector<Vertex> vertices) {
    m_mesh = std::move(vertices);
}

std::vector<Vertex> v = ...;
model->setMesh(std::move(v));

如果您使用临时对象(或已移动的对象)调用函数,将不会进行复制。 但是,如果使用左值调用它,它将进行复制并保留原始对象不变。 这意味着您可以选择是复制还是移动某些东西到函数中。

英文:

A std::vector gives you exclusive ownership over a dynamically sized array of elements, which is exactly what you want to do. It simplifies things as well by allowing you to save on the error-prone explicit size variable. I'd say go for it.

Using a vector won't introduce much overhead either: It's just a pointer to some memory with an added size and capacity. (In practice, these are also implemented as pointers, so a vector is just three pointers.)

Make sure to call reserve with the appropriate size first if you're going to push_back into the vector:

vertices.reserve(numVertices);
for (size_t i = 0; i &lt; numVertices; ++i) {
    // load vertex
    vertices.push_back(vertex);
}

Otherwise, the vector's default growth policy might make it bigger than you need it to be, wasting memory. (If I recall correctly, a vector doubles in size by default whenever its allocated storage becomes full.)

If you want to memcpy into the vector, you have to resize it first to allocate an appropriate amount of storage.

To pass the vector around without making copies, use std::move. It'll boil down to the same operations as your unique_ptr - just shuffling a couple of pointers around. You can copy the vector easily when you want to, but you don't have to.

If you want to make a function or constructor that "consumes" a vector (takes ownership of it), just take it by value and move it to its final destination:

void Model::setMesh(std::vector&lt;Vertex&gt; vertices) {
    m_mesh = std::move(vertices);
}

std::vector&lt;Vertex&gt; v = ...;
model-&gt;setMesh(std::move(v));

No copy is ever made there if you call the function with a temporary (or moved object). If you call it with an lvalue, though, it'll make a copy and leave the original object intact. This means that you can choose whether to copy or move something into the function.

huangapple
  • 本文由 发表于 2023年2月9日 02:19:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75390118.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定