Умные указатели для представления графа (соседи вершин) в C++11

Мне было интересно, как правильно использовать интеллектуальные указатели С++ 11 для графических представлений.

Предположим, у вас есть структура графа, которая содержит вектор всех его вершин. Кроме того, у вас есть структура/класс вершины. Эта вершина содержит вектор всех своих соседей (список смежности).

Мой вопрос: какие типы указателей/умных указателей следует использовать для представления этого графика?

Я читал, что для бинарных деревьев для родительских узлов вы должны использовать необработанные указатели. Потому что узел не владеет своим родителем. Дочерние элементы бинарного дерева могут быть представлены std::unique_ptr, поскольку узел владеет дочерними элементами.

Но в графе несколько узлов могут иметь общих соседей. Итак, должен ли я использовать для этого std::shared_ptr? Или я все равно должен использовать необработанные указатели?


person n2k    schedule 07.12.2014    source источник


Ответы (1)


Сначала необходимо разработать стратегию владения узлом (или ребром).

Например, в примере с бинарным деревом, который вы размещаете, родитель владеет своими дочерними узлами, но не своим родительским узлом. Такой дизайн гарантирует, что каждый узел в дереве принадлежит ровно одному другому узлу, за исключением корневого узла, из которого вы можете сделать особый случай. Поскольку в этом примере у каждого узла есть только один владелец (его родитель), unique_ptr можно использовать для моделирования этой связи. Кроме того, в этом примере ссылка от дочернего объекта к родительскому является ссылкой без владения, поэтому ее нельзя смоделировать с помощью интеллектуального указателя-владельца.

В примере с двоичным деревом ваш граф-владелец является ациклическим, направленным, и каждый узел указывает на только один раз (с помощью указателей-владельцев).

В более сложном примере граф может быть ациклическим, направленным, но узлы могут быть указаны несколько раз. Такой граф мог бы использовать shared_ptr для моделирования ссылок, на которые указывают, поскольку такие ссылки совместно владеют указателем.

Однако надо быть осторожным. Как только ваш график станет циклическим, shared_ptr больше нельзя будет использовать исключительно. Каждый раз, когда вы устанавливаете цикл владения:

A owns B which owns C which owns A

то вы устроили утечку памяти. Такие циклы можно создавать с помощью shared_ptr или unique_ptr. Но на практике циклы, как правило, происходят чаще при использовании shared_ptr, вероятно, просто потому, что диаграмма владения по своей сути более сложна, чем unique_ptr.

shared_ptr содержит вспомогательный класс для разрушения шаблонов циклического владения: weak_ptr. Это можно использовать для настройки чего-то вроде:

A owns B which owns C which has a (non-owning) weak_ptr to A

Если граф неориентированный, и вы моделируете узлы A и B с помощью:

A points to B and B points to A

то вы сразу устанавливаете цикл, и поэтому оба эти указателя не могут быть указателями-владельцами. В такой ситуации вам придется проектировать, что чем владеет. Возможно, полностью отдельный фрагмент кода может владеть всеми узлами, а все ребра могут быть представлены невладеющими указателями. Возможно, граф можно разделить на набор ациклических направленных ребер (представляющих указатели-владельцы) и всех остальных ребер (указатели, не являющиеся владельцами). Действительно, это именно то, что мы сделали с бинарным деревом — владея дочерними указателями и не владеющими родительскими указателями.

Каким бы ни был ваш дизайн, как только он будет завершен, он приведет вас к тому, являются ли shared_ptr и/или unique_ptr подходящими инструментами для реализации вашего дизайна. Если что-то всегда будет принадлежать одному человеку, то unique_ptr является приемлемым вариантом. Если что-то должно принадлежать нескольким другим объектам, shared_ptr может быть правильным инструментом (и определенно не unique_ptr). Если граф владения shared_ptr содержит циклы, их можно разорвать, заменив некоторые из этих связей на weak_ptr. Если вам не удастся обнаружить и разорвать циклы в графе владения, циклы приведут к утечкам памяти.

person Howard Hinnant    schedule 08.12.2014
comment
@HerbSutter только что поставил небольшой вызов на его сайте, решение будет раскрыто на cppcon16 - person TemplateRex; 18.09.2016
comment
В общем, кажется, что это не работает, что, если все родители shared_ptr узла удалены, но все еще существуют ссылки weak_ptr с других узлов. В таком случае узел будет удален непреднамеренно. - person somebody4; 15.07.2020