4

When should I use shared_ptr and when unique_ptr?

For example in this class instead of node* should be shared_ptr or unique_ptr. What it depends on?

class node
{
private:
    node *parent;
    vector<node*> children;

    /*
     * txny
     * x - numer drzewa
     * y - numer wezla
     */
    string id;
    typeNode type; //0 - term, 1 - func

public:
    node(node* parent, string id, typeNode type);
    virtual ~node() {}

    void addChild(node* child);
    void setChildren(vector<node*> &children);
    node* getChild(int i);
    int getChildrenNumber();
    void setParent(node* parent);
    node* getParent();
    string getId();
    typeNode getType();
    void setId(string id);
}; 

EDIT:

Class tree owns node object. I must write more text cause cant save changes.

class tree
{
private:
    vector <node*> nodes;
    int depth;
    int counterNodes;
    /*
     * pxty
     * x - numer populacji (generacji)
     * y - numer drzewa
     */
    string id;
private:
    node* root;
    node* parentGeneticPoint;
public:
    tree(int depth, string id);
    tree(int depth);
    ~tree();
public:
    void initialize(typeInit type);
    void showVector();
    void show();
    Mat run();
    tree* copy();
    tree* copySubtree(int subrootI);
    tree* copyWithoutSubtree(int subrootI);

};

3 Answers3

7

In the case of this tree structure, you should have shared_ptr to the children and weak_ptr for the parents.

Why ?

  • shared_ptr allows other objects to point to the children as well (for example if you keep somewhere a pointer to a subtree).
  • weak_ptr is similar to a shared_ptr but doesn't take ownerhip. So here if a node is no longer pointed to by its parent or other shared_ptr, it will be destroyed. If its own children would have a shared_ptr, the object would never be destroyed due to the cirular reference.

unique_ptr should be used, if only one object shall be responsible for the pointer and its deletion. This means in particular that you're not supposed to make a copies of the pointer. You can however transfer its ownership.

Edit: additional infos

The comments show that the matter is more complex than this answer. After all, there are full book chapters devoted to this topic ;-) Three questions shall help you to choose the right design: who owns the object pointed to ? Will pointers be given to the outside word ? What guarantees do you want to give about these pointers ?

If you want other objects (i.e. outside the node structure) to point safely to your nodes (i.e. you don't want the node to disapear while they are used outside), you'll need shared_ptr. A node will be deleted when no shared_ptr refers to it anymore.

If on the contrary, a node "owns" its children and is sole responsible for their destruction, the unique_ptr could be the option to go. unique_ptr::get() can be used to get (raw) pointers to nodes, but without guarantee that they will be remain valid in a later time.

Christophe
  • 54,708
  • 5
  • 52
  • 107
  • 5
    If the parent node owns the child, `unique_ptr` is the right choice, not `shared_ptr`. You can share a non-owned pointer to a `unique_ptr` subtree using `std::unique_ptr::get()`. "unique_ptr should be used, if only one object shall use the pointer" is incorrect. `unique_ptr` should be used if only one object shall *own* the pointer. Ownership and use are very different. – anthonyvd Aug 17 '15 at 15:57
  • That makes sense. Thanks. If i had other class, which contains vector forest and I use unique_ptr, can I have functions like void addTree(unique_ptr) and unique_ptr getTree() ? –  Aug 17 '15 at 15:59
  • @AnthonyVallée-Dubois I don't think it should be oversimplified either. `unique_ptr::get()` returns a raw pointer, So your node can be deleted, even if a subtree pointer still might need it. The real question is what pointers do you intend to give to the outside world, with what guarantees. I'll edit to clarify. – Christophe Aug 17 '15 at 16:06
  • @AnthonyVallée-Dubois It is correctly if one resource have two pointers - first shared_ptr, second unique_ptr? And what it difference between `own` and `use`? In this case class node owns or use pointer to parent? –  Aug 17 '15 at 16:10
3

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 ).

Enlico
  • 12,203
  • 5
  • 28
  • 59
Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465
  • As in my code. I have in class node member `vector children` and in class tree `vector nodes`. In `nodes` I store all pointers to node in my tree structure. In one of this node `children` store pointers to the some of this nodes. So shouldnt it be `shared_ptr`? Or `vector< unique_ptr > children` and `vector nodes`? –  Aug 17 '15 at 20:12
  • 1
    I assume your parents own your children. So the parent should have a vector of unique pointers to childen... The childs pointer-to-parent can either be eliminated, or can be a raw pointer (non-owning), which is only possibly invalid during child destruction. On the other hand, if you want the tree to own the nodes, then it should have the vector of `unique_ptr`, and the pointers in the tree nodes themselves should be raw pointers. Either is good. – Yakk - Adam Nevraumont Aug 17 '15 at 20:26
  • It should be `vector children` in class node and `vector> nodes` in class tree. Because if it was inversely and I delete one of the node in `nodes`, it will be also delete all children. Am I right? –  Aug 17 '15 at 20:39
  • 2
    @user3191398 Really, your design is a bit of a mess: the vector of nodes in `tree` seems not that useful. I'd kill it myself, and leave only each node having `unique_ptr`s to their children. And a single `unique_ptr` to the root node of the tree in `tree`. That coupling (between that list of pointers, and the pointers in the tree) will increase complexity and make bugs more likely. – Yakk - Adam Nevraumont Aug 17 '15 at 20:43
  • But this vector of nodes is helpful to get random node from tree. –  Aug 17 '15 at 20:47
  • 1
    @user3191398 nodes could keep track of how many children they have. Then picking a node at random is pretty quick even without that vector: pick a random number from 0 to # children of root node inclusive. Then get that node (where 0 is the root node). (assuming a somewhat balanced tree, this is lg(n)). – Yakk - Adam Nevraumont Aug 17 '15 at 21:01
  • Sorry, I dont understand your way to pick random node. But thanks, I Iearn something about smart ptr –  Aug 17 '15 at 21:16
  • 1
    @user319 added a description now to do that as well. – Yakk - Adam Nevraumont Aug 17 '15 at 21:25
  • Can I ask you one more question? If i want to change one node in tree, should I pass it as reference or as value in function? Is it correct? http://www.wklej.org/id/1777112/ –  Aug 18 '15 at 20:41
1

Whenever possible, use unique_ptr (beware of moving/relinquishing ownership though), shared_ptr has an extra cost of memory and synchronization, besides, having a single place of ownership of a resource is preferable from the design point of view.

imreal
  • 9,700
  • 2
  • 28
  • 45