У меня есть ориентированный ациклический граф, реализованный классами Graph и Node. У каждого узла есть список указателей на детей и список указателей на родителей. Я недавно добавил родителей, потому что некоторые алгоритмы требовали быстрого доступа к списку родителей, а график маленький, всего несколько соединений на узел, поэтому проблем с памятью нет.
Список Child использует std :: shared_ptr, так что узлы хранятся в памяти, по крайней мере, до тех пор, пока у них есть родители. Но я не хочу, чтобы узел владел своими родителями, поэтому я использовал weak_ptr для указателей на родителей.
Но тогда возникла проблема с алгоритмами. Алгоритм должен создать новый shared_ptr из weak_ptr, поэтому я не могу напрямую использовать operator ==, а использование стандартных функций, таких как std :: find (), требует написания лямбда-функции, которая вызывает my_weak_ptr.lock (), и затем сравнивает ее к некоторому shared_ptr.
Если я переключусь на shared_ptr, любая небольшая ошибка в коде, отвечающем за удаление узла, может привести к утечке памяти. Или, если у меня есть указатель на уже удаленный узел, код сможет получить доступ к узлу, который не должен существовать, поэтому найти некоторые ошибки может стать намного сложнее. Но работа с shared_ptr так же безопасна, как weak_ptr, с точки зрения не разыменования / удаления / и т.д. когда не предполагается, (так что это лучше, чем необработанный указатель C ++) и std :: find () могут быть использованы напрямую, потому что shared_ptr может быть разыменовано, в отличие от weak_ptr.
Есть ли здесь «лучший» дизайн, или это проблема этой конкретной ситуации, в зависимости, например, от насколько это важно, если я сделаю дополнительную операцию weak_ptr :: lock () или рискну найти трудно обнаруживаемые ошибки?
Как вы сами сказали, используя shared_ptr
в обоих направлениях будут создаваться круги, которые создают утечки памяти, которые трудно найти и сломать — вы потеряете (почти) все преимущества, которые предоставляет shared_ptr. Так weak_ptr
это должно быть.
Вы говорите, что ваши алгоритмы должны заблокировать weak_ptr
— Позволю себе не согласиться. Алгоритмы должны получить родителя shared_ptr
от узла. Задача узла — заблокировать родителя weak_ptr
и вернуть результат, либо правильно установленный родительский узел или NULL.
Это деталь реализации, где узлы хранят своих родителей как shared_ptr
или же weak_ptr
, Инкапсулируйте эту деталь, предоставляя только shared_ptr
с любым клиентам.
class Node
{
/* ... */
std::weak_ptr<Node> parent;
public:
std::shared_ptr<Node> getParent()
{
return parent.lock();
}
};
Редактировать:
Конечно, концептуально то же самое применимо, если есть более одного родителя.
Edit2:
В комментариях вы упоминаете алгоритмы, перебирающие список ваших родителей, что делает необходимым писать лямбда-выражения для каждого алгоритма. Если вы часто используете эти алгоритмы, попробуйте написать адаптер итератора, который автоматически блокирует цель 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);
Большинство направленных ациклических графов вообще не должны нуждаться в слабых указателях для своих родителей, а должны работать с простыми указателями. В обоих случаях каждый узел обязан удалить себя из родительского списка каждого клиента, как только он будет удален. Если вам нужно извлечь общий указатель из родительского указателя в какой-то особой ситуации, вы можете использовать std :: shared_from_this аналогично тому, как вы использовали бы lock () прямо сейчас. Таким образом, вы сохраняете работу для создания и обработки общих указателей повсюду, но только там, где они вам нужны.