shared_ptr
solves memory management problems the way regular expressions solve html parsing problems.
shared_ptr
can be part of a solution for a lifetime management issue, but it is in no way, shape or form something to be used casually. It is extremely easy to have "misplaced" pointers, or reference loops, with shared_ptr
. In my experience, use shared_ptr
as an internal private implementation detail with guards, invariants and axioms that together prove that you cannot form loops, and you have a decent chance of not having problems.
Over half of my use of shared_ptr
consists of a single location that "owns" the pointer, and other observers that have weak_ptr
s except for narrow windows when they check that the resource is still around, plus reason to think that the shared_ptr
won't die in that narrow window.
Another good chunk of use is when I have a copy-on-write situation, where I have a nearly-immutable object state that can be copied around (stored in a shared_ptr<const T> pimpl
. When a write operation occurs, if I'm the only user I cast that to a shared_ptr<T>
and modify it. Otherwise, I copy it into a shared_ptr<T>
and modify it. Then I store it back as the shared_ptr<const T>
in both cases.
Simply scattering shared_ptr
s around in my experience inevitably leads to leaks and resources lasting far longer than they should.
On the other hand, you should just use unique_ptr
. make_unique
and unique_ptr
should replace new
and delete
in your code in nearly every circumstance. It is really, really hard to get unique_ptr
wrong, and when you do, it is usually because the old code had a serious leak risk, or you didn't understand how the resource was being managed before.
In new code, it is a no-brainer. In old code, if you need to understand the lifetime of the objects involved, you will have to learn enough to put the ownership in a unique_ptr
probably anyhow. The big exception is when you are doing "cargo cult" programming (modifying a complex system you don't understand in ways that look like the other code in the system, and hoping that it will work because the other code worked) is not feasible to manage resource that way. There are also some other exceptions, like objects who manage their own lifetime in a complex way.
Managing non-new
'd resources with unique_ptr
is a touch harder, but I also find worth it.
And sometimes you are forced to .release
the pointer into a long C-style void*
filled call-chain.
There is also a run-time overhead on shared_ptr
, but the conceptual overhead of shared_ptr
making object lifetime much harder to understand is the real reason to avoid using it.
shared_ptr
can be used to do fancy things, but it does not solve your problem, it is a tool that as part of a comprehensive resource management system you can use to make your solution a bit simpler.
There is nearly zero run-time overhead on unique_ptr
: it is the same size as a pointer, and it simply calls delete
at end of lifetime.
unique_ptr
solves entire resource management problems dead in their tracks. They evaporate once you use it properly.
In your specific example, I'd either put unique_ptr
s in tree
, and keep raw pointers in nodes.
Alternatively, keep unique_ptr
s in the children
vector, and raw pointers in the tree and in the parent pointer.
In either case, all operations that add/remove nodes should go through tree
(taking a node argument for the target), as the state of tree
and the node
s needs to be kept in sync.
You expressed an interest in getting a random node when I pointed out that the list of nodes in the root tree
was a bad idea.
Simply store the number of children in each node. (This requires work when you add/modify/delete children that must cascade up to the root).
Add:
node* node::nth_node( int n ) {
if (n == 0) return this;
--n;
for( auto&& child:children ) {
if (n < child->subtree_size)
return child->nth_node(n);
n -= child->subtree_size;
}
return nullptr; // n is too big
}
this gets the nth descendent of a node, assuming subtree_size
is how big a tree node
is a root of (including itself, so it should never be 0
).
To get a random node from tree
, create a random number from 0
to root->subtree_size
. Ie, if root->subtree_size
is 3
, your random number is 0
, 1
or 2
.
Then call root->nth_node( that_random_number )
.