shared_ptr,unique_ptr,ownership,在这种特殊情况下我是否过度了?

时间:2016-09-02 07:07:51

标签: c++ c++11 c++14 shared-ptr

我从事图形应用,并一直使用共享和放大器。唯一指针本质上是因为它为我处理内存释放(也称为方便),这可能很糟糕(如果这就是我使用它们的原因)。

我最近阅读了Stackoverflow上一个问题的答案,提到根据B. Stroustrup,通常不应该使用唯一/共享ptrs,而且应该通过值传递参数。

我有一个图形案例,我认为使用shared_ptr是有道理的但我想知道专家(我不是C ++专家),如果我过度做/思考它,如果是这样他们会做什么而不是(符合C ++建议和效率)

我将解决渲染/光线跟踪中出现的一般问题。在这个特殊的问题中,我们有一个对象池(我们将使用三角形进行此解释)和一个结构,为了简化说明,我们将其称为常规3D网格。让我们说在某些时候我们需要将三角形插入到网格中:这意味着我们需要检查每个插入三角形的边界体积是否与网格中的任何单元格重叠,然后是每个重叠的单元格需要保持指向该三角形的指针/引用(供以后使用)。一个三角形可能会重叠超过1个单元格,因此它可以被多个单元格多次引用(您可以看到我在这里使用shared_ptr的位置)。

请注意,在网格结构之外,我们不需要三角形池(所以从技术上讲,这里拥有三角形池的对象是网格,或者更确切地说是网格的单元格)。

class Grid
{
    struct Cell
    {
        std::vector<std::shared_ptr<const Triangle>> triList; 
    };
    void insert(triangle*& tri_)
    {
        std::shared_ptr<const Triangle> tri = tri_;
        for (each cell overlapped by tri) {
            // compute cell index
            uint32_t i = ...
            cells[i].triList.push_back(tri);
        }
    }
    Cell cells[RES * RES * RES];
};

void createPoolOfTrianglesAndInsertIntoGrid()
{
    Grid grid;
    uint32_t maxTris = 32;
    Triangle* tris = new Triangles[maxTris];
    // process the triangles
    ...
    // now insert into grid
    for (uint32_t i = 0; i < maxTris; ++i) {
        // use placement new
        Triangle* tri = new (&tris[i]) Triangle;
        grid.insert(tri);
    }
    // no need to delete tris here ... it should be done by 
    // the grid when we go out of this function's scope
}

这听起来很复杂,但我的设计背后的动机是遵循Stroustrup的建议。在这种情况下,函数createPoolOfTrianglesAndInsertIntoGrid不拥有三角形,它是网格的单元格。所以我在函数createPoolOfTrianglesAndInsertIntoGrid中分配内存,因为这是我需要创建三角形的地方,然后我使用placement new方法获取指向该池中每个三角形的指针,然后我可以将其传递给网格insert方法(我将该对象的内存管理推迟到该方法)。在那里,它将三角形转换为shared_ptr,单元格现在可以共享一个“引用”(使用shared_ptr)。

我想知道你是否认为这是正确的方向,或者如果这看起来完全错误,无论是在实施方面还是在可能的效率损失方面(我分配了一个内存池,然后使用用于创建临时三角形的新位置,然后我将其传递给网格插入方法,然后转换为shared_ptr,...)

我正在努力学习和改进我的代码,并希望您能提供有价值的专业反馈。

编辑:基本上我正试图找到解决该问题的正确方法+我将根据您的意见尝试稍后进行一些修改

3 个答案:

答案 0 :(得分:4)

我认为你的Grid拥有三角形。我假设三角形相对较轻(3-5维?)。

我认为这样的事情可能适合。我在Grid中使用容器来获取三角形按值的所有权。当Grid超出范围时,容器将删除三角形。

然后每个Cell只使用原始指针来跟踪它引用的三角形。 Cell不拥有三角形,只是指向Grid所拥有的三角形的指针。

class Grid
{
    struct Cell
    {
        std::vector<Triangle*> triList; // non owning
    };

    void insert(Triangle tri) // pass by value
    {
        tris.push_back(tri); // Grid owns this by value

        for(each cell overlapped by tri) {
            // compute cell index
            uint32_t i = ...

            cells[i].triList.push_back(&tris.back());
        }
    }

    // Use a deque because it won't re-allocate when adding
    // new elements to the end
    std::deque<Triangle> tris; // elements owned by value

   Cell cells[RES * RES * RES]; // point to owned elements
};

void createPoolOfTrianglesAndInsertIntoGrid()
{
    Grid grid; // owns the triangles (by value)

    uint32_t maxTris = 32;

    std::vector<Triangle> tris(maxTris);
    // process the triangles
    // ...
    // now insert into grid
    for(auto tri: tris)
        grid.insert(tri);
    }

    // no need to delete tris here ... it should be done by
    // the grid when we go out of this function's scope
}

注意:我使用std::dequeGrid中的值存储三角形。那是因为在添加新三角形时它不会重新分配其内部存储器。如果你在std::vector处使用std::vector,那么当Grid调整自身大小时,你的原始指针就会悬空。

<强>可替换地:

鉴于您在函数中构建了所有三角形,然后将所有三角形传递到class Grid { struct Cell { std::vector<Triangle*> triList; // non owning }; // Accept the entire container in-tack // (no reallocations allowed after this point) void insert(std::vector<Triangle> tris) // pass by value (able to move in) { // for(auto& tri: tris) { for(each cell overlapped by tri) { // compute cell index uint32_t i = ... cells[i].triList.push_back(&tri); } } } // Using a vector so it MUST NOT be resized after // Cells have been pointed to its elements!!! std::vector<Triangle> tris; // elements owned by value Cell cells[RES * RES * RES]; // point to owned elements }; void createPoolOfTrianglesAndInsertIntoGrid() { Grid grid; // owns the triangles (by value) uint32_t maxTris = 32; // Build the triangles into std::vector<Triangle> tris(maxTris); // process the triangles // ... // now insert into grid grid.insert(std::move(tris)); // move the whole darn container (very efficient) // no need to delete tris here ... it should be done by // the grid when we go out of this function's scope } ,为什么一次只能这样做?你可以一次性通过整个容器。如果你使用 move semantics 执行此操作,则甚至不需要复制任何内容:

std::vector

注意:现在我使用了Grid因为您在到达Grid后没有逐个添加三角形。但是你必须确保里面 """ Usage: test.py --list=(all|available) Options: list Choice to list devices (all / available) """ 拥有的`std :: vector没有调整大小。

答案 1 :(得分:1)

高效的内存分配是对象生命周期和对象行为的一个单独问题。

控制分配策略的机制是Allocatorstd::vector<Type, Allocator>中的std::allocate_shared<Type, Allocator>

看起来您想要从池中执行分配。

首先,给出Triangle值语义(即不保存指向它的指针)将允许std::vector有效地分配内存块。它的分配策略假定需要多个对象。在封面下,它分配内存块并在适当时调用就地构造函数/析构函数。

如果Triangle确实需要是一个共享句柄(即在shared_ptr方面),那么你可以使用自定义分配器来分配shared_ptr。

Boost有一些内存池分配器的例子。

答案 2 :(得分:0)

如果单元格是三角形的唯一所有者,那么您不需要池,只需将三角形分配为shared_ptr并将它们插入网格中。这样,所有不再被任何单元格拥有的三角形都将被清除。