10

我有一个由 Graph 和 Node 类实现的有向无环图。每个节点都有一个指向子节点的指针列表和一个指向父节点的指针列表。我最近添加了父母,因为一些算法需要快速访问父列表并且图形很小,每个节点只有几个连接,所以没有内存问题。

Child 列表使用 std::shared_ptr 以便节点至少只要有父节点就保存在内存中。但我不希望一个节点拥有它的父母,所以我使用了weak_ptr作为指向父母的指针。

但随后算法出现了问题。算法必须从weak_ptr创建一个新的shared_ptr,所以我不能直接使用operator==,并且使用标准函数如std::find()需要编写一个名为my_weak_ptr.lock()的lambda函数,然后比较它到一些shared_ptr。

如果我切换到 shared_ptr,负责删除节点的代码中的任何小错误都可能导致内存泄漏。或者,如果我有一个指向已删除节点的指针,代码将能够访问一个不应该存在的节点,因此查找一些错误可能会变得更加困难。但是在不取消引用/删除/等方面,使用 shared_ptr 与 weak_ptr 一样安全。当不应该时,(所以它比原始 C++ 指针更好)和 std::find() 可以直接使用,因为 shared_ptr 可以被取消引用,不像weak_ptr。

这里是否有“更好”的设计,或者这是这种特定情况的问题,这取决于例如,如果我执行weak_ptr::lock() 的额外操作或冒着难以找到的错误的风险,这有多重要?

4

2 回答 2

12

正如您自己所说,shared_ptr在两个方向上使用都会创建导致内存泄漏并且很难找到和破坏的圈子 - 您将失去(几乎) shared_ptr 提供的所有好处。weak_ptr应该是这样。

你说你的算法必须锁定weak_ptr- 我不同意。算法必须从节点获取父shared_ptr节点。锁定父节点weak_ptr并返回结果是节点的任务,可以正确设置为父节点,也可以设置为 NULL。

节点是否将其父节点存储为shared_ptr或是一个实现细节weak_ptrshared_ptr通过仅向任何客户提供 s 来封装该细节。

class Node
{
  /* ... */
  std::weak_ptr<Node> parent;
public:
  std::shared_ptr<Node> getParent()
  {
    return parent.lock();
  }
};

编辑: 当然,如果有不止一个父母,在概念上同样适用。

Edit2: 在评论中,您提到算法迭代您的父母列表,因此有必要为每个算法编写 lambda。如果您经常使用这些算法,请考虑编写一个迭代器适配器,它会自动锁定目标weak_ptr并返回shared_ptr

template <class WPIterator>
struct LockTheWeakIterator
{
  //static_assert that WPiterator's value_type is some weak_ptr
  //typedef all those iterator typedefs
  typedef typename WPIterator::value_type::element_type element_type;

  shared_ptr<element_type> operator*()
  { return iter->lock(); }

  //provide all the other operators - boost.operators might help with that...

  WPIterator iter;
};

template <class IT>
LockTheWeakIterator<It> lockTheWeak(It iter);


//somewhere...
auto theParentIter = std::find_if(lockTheWeak(parents.begin()), 
  lockTheWeak(parents.end()), 
  whatIAmLookingFor);
于 2013-01-28T13:41:16.603 回答
3

大多数有向无环图根本不需要指向其父级的弱指针,而是使用普通指针。在这两种情况下,一旦被删除,每个节点都有责任将自己从每个客户端的父列表中删除。如果您需要在某些特殊情况下从某个父指针检索共享指针,您可以使用 std::shared_from_this 以与您现在使用 lock() 类似的方式。通过这种方式,您可以节省创建和处理共享指针的工作,但仅限于您需要它们的地方。

于 2013-01-28T14:02:04.413 回答