As you said yourself, using shared_ptr
in both directions will create circles that create mem leaks and are hard to find and to break - you will lose (nearly) all of the benefits shared_ptr provides. So weak_ptr
it shall be.
You say your algorithms have to lock the weak_ptr
- I beg to differ. The algorithms have to get a parent shared_ptr
from the node. It's the node's task to lock the parent weak_ptr
and give back the result, either set correctly to a parent node or to NULL.
It's an implementation detail wether the nodes store their parents as shared_ptr
or weak_ptr
. Encapsulate that detail by only providing shared_ptr
s to any clients.
class Node
{
/* ... */
std::weak_ptr<Node> parent;
public:
std::shared_ptr<Node> getParent()
{
return parent.lock();
}
};
Edit: Of course conceptually the same applies if there's more than one parent.
Edit2:
In the comments you mention algorithms iterating over your parents list, making it necessary to write lambdas for each algorithm. If you use those algorithms often, consider to write an iterator adapter that automatically locks the target weak_ptr
and gives back a 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);