Прерывание циклических ссылок с помощью std::weak_ptr и псевдонима конструктора: звучит или проблематично?

Я еще не нашел следующий способ разрыва циклических ссылок, описанный на каком-либо крупном форуме / блоге C++, например, на 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 не заметит хитрости с использованием конструктора псевдонимов в getLeft а также getRight, но все же пользователь может быть уверен, что getParent всегда возвращает непустой общий указатель, потому что все указатели возвращаются 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()!
}

1 ответ

Решение

shared_ptr<Node> вернулся вашим getParent владеет родителем, а не родителем родителя.

Таким образом, призывая getParent еще раз об этом shared_ptr может вернуть пустой (и ноль) 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, который владеет узлом, а не его родителем, но я полагаю, что вам будет сложнее испортить приватное объявление об использовании и запретить его по контракту.)

Другие вопросы по тегам