使用 std::weak_ptr 和别名构造函数打破循环引用:听起来还是有问题?

Breaking cyclic references with std::weak_ptr and alias constructor: sound or problematic?

我还没有找到在任何主要的 C++ forum/blog 上解释的以下打破循环引用的方法,比如在 GotW 上,所以我想问一下这种技术是否已知,它的优缺点是什么?

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

   // the getter functions ensure that "parent" always stays alive!
   std::shared_ptr<Node> getLeft() {
       return std::shared_ptr<Node>(shared_from_this(), left.get());
   }

   std::shared_ptr<Node> getRight() {
       return std::shared_ptr<Node>(shared_from_this(), right.get());
   }

   // add children.. never let them out except by the getter functions!
public:
   std::shared_ptr<Node> getOrCreateLeft() {
       if(auto p = getLeft())
          return p;
       left = std::make_shared<Node>();
       left->parent = shared_from_this();
       return getLeft();
   }

   std::shared_ptr<Node> getOrCreateRight() {
       if(auto p = getRight())
          return p;
       right = std::make_shared<Node>();
       right->parent = shared_from_this();
       return getRight();
   }

private:
   std::weak_ptr<Node> parent;
   std::shared_ptr<Node> left;
   std::shared_ptr<Node> right;
};

从外部看,Node 的用户不会注意到在 getLeftgetRight 中使用别名构造函数的技巧,但用户仍然可以确定 getParent 总是 returns 一个非空的共享指针,因为 p->get{Left,Right} 返回的所有指针在返回的子指针的生命周期内保持对象 *p 存活。

我是否忽略了这里的某些内容,或者这是打破已经记录在案的循环引用的明显方法?

int main() {
   auto n = std::make_shared<Node>();
   auto c = n->getOrCreateLeft();
   // c->getParent will always return non-null even if n is reset()!
}

getParent 编辑的 shared_ptr<Node> return 拥有父项,而不是父项的父项。

因此,在 shared_ptr 上再次调用 getParent 可以 return 一个空的(和 null)shared_ptr。例如:

int main() {
   auto gp = std::make_shared<Node>();
   auto p = gp->getOrCreateLeft();
   auto c = p->getOrCreateLeft();
   gp.reset();
   p.reset(); // grandparent is dead at this point
   assert(c->getParent());
   assert(!c->getParent()->getParent());
}

(继承的 shared_from_this 也传出拥有该节点而不是其父节点的 shared_ptrs,但我想你可以通过 private using 声明使它更难搞砸并禁止它按合同。)