vendredi 2 septembre 2016

shared_ptr, unique_ptr, ownership, Am I overdoing it in this particular case?

I work on graphics applications and have been using shared & unique pointers essentially because it handles memory deallocation for me (aka as a convenience) which is probably bad (if that's the reason why I use them).

I read the answer to a question on Stackoverflow recently that mentioned that according to B. Stroustrup, unique/shared ptrs should generally not be used and that arguments should be passed by value instead.

I have a case in graphics for which I think using shared_ptr makes sense but I'd like to know from experts (I am not C++ experts) if I am over doing and if so what they would be doing instead (to conform to C++ recommendations and for efficiency)

I will take a general problem that occurs in Render/Ray-Tracing. In this particular problem we have a pool of objects (we will use triangles for this explanation) and a structure which for the simplicity of the explanation we will refer to as regular 3D grid. Let's say that at some point we need to insert the triangles into the grid: what that means is that we need to check of the bounding volume of each inserted triangle overlaps any of the cells from the grid, and it does, then each overlapped cell needs to keep a pointer to that triangle. A triangle may overlap more than 1 cell, so it can be referenced multiples times by several cells (you see where I am going with the shared_ptr here).

Note that outside of the grid structure, we don't need to keep the pool of triangles (so technically the object that owns the pool of triangle here, is the grid).

class Grid
{
    struct Cell
    {
        std::vector<std::shared_ptr<const tri>> 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) {
        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
}

It sounds complicated but my motivation behind this design is to follow Stroustrup's recommendation. In this case the function createPoolOfTrianglesAndInsertIntoGrid doesn't owned the triangles, it's the cells of the grid that do. So I allocate the memory in the function createPoolOfTrianglesAndInsertIntoGrid because this is where I need to create the triangles, and then because I pass each triangle to the insert method of the Grid class. In there, it converts the triangle into a shared_ptr and cells can now share a "reference" to it.

I wanted to know if to your opinion this was going in the right direction, or if this appears completely wrong, both in terms of implementation and in terms of possible loss of efficiency (I allocate a pool of memory, then use the new placement to create a temp triangle, which I then pass on to the grid insert method, then convert to shared_ptr, ...)

I am trying to both learn, and improve my code and hope you can provide valuable professional feedback.

Aucun commentaire:

Enregistrer un commentaire