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_ptrs 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_ptrs 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_ptrs in tree, and keep raw pointers in nodes.
Alternatively, keep unique_ptrs 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 nodes 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 ).