我最近读到了关于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方法获取指向该池中每个三角形的指针,然后我可以将其传递给网格插入方法(I将该对象的内存管理推迟到该方法).在那里,它将三角形转换为shared_ptr,并且单元格现在可以共享它的“引用”(使用shared_ptr).
我想知道你是否认为这是正确的方向,或者如果这看起来完全错误,无论是在实施方面还是在可能的效率损失方面(我分配了一个内存池,然后使用新的位置来创建一个临时三角形,然后我将其传递给网格插入方法,然后转换为shared_ptr,…)
我正在努力学习和改进我的代码,并希望您能提供有价值的专业反馈.
解决方法
我认为这样的事情可能适合.我在Grid中使用一个容器来按值取得三角形的所有权.当Grid超出范围时,容器将删除三角形.
然后每个Cell只使用原始指针来跟踪它引用的三角形. Cells不拥有三角形,只是保持指向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 :: deque在Grid中按值存储三角形.那是因为在添加新三角形时它不会重新分配其内部存储器.如果你在这里使用了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 }
注意:现在我使用了std :: vector,因为你没有在到达Grid之后逐个添加三角形.但是你必须确保在Grid内部拥有的`std :: vector没有调整大小.