Weak_ptr VS shared_ptr в графе node родительский список
У меня есть ориентированный ациклический граф, реализуемый классами Graph и Node. Каждый Node имеет список указателей на childern и список указателей на родителей. Недавно я добавил родителей, потому что некоторые алгоритмы требовали быстрого доступа к родительскому списку, а график небольшой, всего несколько соединений на node, поэтому проблем с памятью не возникает.
В списке Child используется std:: shared_ptr, так что узлы хранятся в памяти, по крайней мере, до тех пор, пока у них есть родители. Но я не хочу, чтобы Node владел родителями, поэтому я использовал weak_ptr для указателей на родителей.
Но тогда возникла проблема с алгоритмами. Алгоритм должен создать новый shared_ptr из weak_ptr, поэтому я не могу напрямую использовать operator ==, а использование стандартных функций, таких как std:: find(), требует записи лямбда-функции, которая называется my_weak_ptr.lock(), а затем сравнивает ее к некоторому shared_ptr.
Если я переключусь на shared_ptr, любая небольшая ошибка в коде, который может быть использован для удаления Node, может привести к утечке памяти. Или, если у меня есть указатель на Node, который уже удален, код сможет получить доступ к Node, который не должен существовать, поэтому найти некоторые ошибки могут значительно сложнее. Но работа с shared_ptr столь же безопасна, как и weak_ptr с точки зрения не разыменования/удаления/etc. когда не предполагается, (так что это лучше, чем исходный указатель на С++) и std:: find() можно использовать напрямую, поскольку shared_ptr может быть разыменован, в отличие от weak_ptr.
Существует ли здесь "лучший" дизайн, или это проблема этой конкретной ситуации, в зависимости от, например, сколько имеет значение, если я выполняю дополнительную операцию weak_ptr:: lock() или рискую найти труднодоступные ошибки?
Ответы
Ответ 1
Как вы сказали сами, использование shared_ptr
в обоих направлениях создаст круги, которые создают утечки памяти и их трудно найти и сломать - вы потеряете (почти) все преимущества, предоставляемые shared_ptr. Итак, weak_ptr
должно быть.
Вы говорите, что ваши алгоритмы должны блокировать weak_ptr
- я прошу разницы. Алгоритмы должны получить родительский shared_ptr
из node. Задача node заблокировать родительский weak_ptr
и вернуть результат, либо правильно установить родительский node, либо NULL.
Это деталь реализации, где узлы хранят своих родителей как shared_ptr
или weak_ptr
. Инкапсулируйте эту информацию, предоставив shared_ptr
любым клиентам.
class Node
{
/* ... */
std::weak_ptr<Node> parent;
public:
std::shared_ptr<Node> getParent()
{
return parent.lock();
}
};
Edit:
Конечно, концептуально то же самое имеет место, если имеется более одного родителя.
Edit2:
В комментариях вы упомянули алгоритмы, повторяющие список ваших родителей, что делает необходимым писать lambdas для каждого алгоритма. Если вы часто используете эти алгоритмы, подумайте о том, чтобы написать адаптер итератора, который автоматически блокирует цель weak_ptr
и возвращает shared_ptr
:
template <class WPIterator>
struct LockTheWeakIterator
{
//static_assert that WPiterator 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);
Ответ 2
Большинство ориентированных ациклических графов вообще не нуждаются в слабых указателях на своих родителей, но работают с простыми указателями. В обоих случаях каждая ответственность node должна удаляться из каждого родительского списка клиентов после их удаления. Если вам нужно получить общий указатель с какого-то родительского указателя в какой-то особой ситуации, вы можете использовать std:: shared_from_this так же, как вы бы сейчас использовали lock(). Таким образом, вы сохраняете работу для создания и обработки общих указателей повсюду, но только там, где они вам нужны.