4

I'm trying to get an idea on how to use std::shared_ptr in C++. But it's pretty confusing and I don't understand how to create multiple shared pointers pointing to the same object. Even the documentation and online material is not very lucid.

Following is a small piece of code I wrote to try and understand std::shared_ptr's behavior:

#include <iostream>
#include <memory>
using namespace std;

class Node
{
public:
    int key;
    Node()
    {
        key = 0;
    }
    Node(int k)
    {
        key = k;
    }
};

int main()
{
    Node node = Node(10);

    shared_ptr<Node> ptr1((shared_ptr<Node>)&node);
    cout << "Use Count: " << ptr1.use_count() << endl;

    // shared_ptr<Node> ptr2=make_shared<Node>(node);//This doesn't increase use_count
    shared_ptr<Node> ptr2((shared_ptr<Node>)&node);
    cout << "Use Count: " << ptr2.use_count() << endl;

    if (ptr1 == ptr2)
        cout << "ptr1 & ptr2 point to the same object!" << endl;

    if (ptr1.get() == ptr2.get())
        cout << "ptr1 & ptr2 point to the same address!" << endl;

    cout << "ptr1: " << ptr1 << "  "
         << "ptr2: " << ptr2 << endl;
    return 0;
}

Based on the output I got, both ptr1 and ptr2 pointed to the same Node object but the use_count is 1 for both of them. Even using std::make_shared doesn't work and the program crashes before exiting.

Can you please tell me what I'm doing wrong? And how to create multiple shared_ptr(s) that are pointing to the same object.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Dee Jay
  • 109
  • 1
  • 7
  • 7
    `(shared_ptr)&node` is wrong on multiple levels. – user7860670 Jan 12 '18 at 06:32
  • 2
    You increase the use count by saying `ptr2 = ptr1;`, and you **MUST NOT** take an address of a local variable as it will try to delete at the end of the program, and crash horribly. – Ken Y-N Jan 12 '18 at 06:33
  • In addition to @VTT's comment, you are creating two distinct pointers that are shared pointers. Not a sharing one pointer. – Ahmed Masud Jan 12 '18 at 06:34
  • BTW, this reference site has [a good example](http://en.cppreference.com/w/cpp/memory/shared_ptr) (but the threading makes it a bit more complicated to follow, perhaps). – Ken Y-N Jan 12 '18 at 06:34
  • Although shared pointers are created and passed while creating `ptr1` & `ptr2`, but those are temporary and would get destroyed or get `moved`. – sameerkn Jan 12 '18 at 06:37
  • But, what if I don't want to do ptr1=ptr2 and instead want to create two shared_ptr(s) using the **node** object? @KenY-N – Dee Jay Jan 12 '18 at 06:52
  • Are you coming from Java? Just trying to understand, why you'd write such code in the first place and where such misconceptions about shared_ptr come from. – MikeMB Jan 12 '18 at 08:06
  • @DeeJay if you want to make sure there's not multiple shared_ptr having their own use_count then take a look at [enable_shared_from_this](http://en.cppreference.com/w/cpp/memory/enable_shared_from_this) – PeterT Jan 12 '18 at 17:59

3 Answers3

7

The use_count won't increase when you create shared_ptrs separately (including using make_shared), they're not shared at all. The created shared_ptr knows nothing about other shared_ptrs and the pointers being managed, even the pointers might happen to be the same (note that it might lead to multiple destruction).

You need to tell which shared_ptrs should be shared; the use_count increases when you create shared_ptr from another shared_ptr. e.g.

shared_ptr<Node> ptr1 = make_shared<Node>(10);
cout<<"Use Count: "<<ptr1.use_count()<<endl; // 1

shared_ptr<Node> ptr2(ptr1);
cout<<"Use Count: "<<ptr2.use_count()<<endl; // 2
cout<<"Use Count: "<<ptr1.use_count()<<endl; // 2

BTW: As the comments suggusted, it's dangerous to make a shared_ptr managing a pointer to &node, which points to an object allocated on stack. The following code might match your intent closely.

Node* node = new Node(10);

shared_ptr<Node> ptr1(node);
cout<<"Use Count: "<<ptr1.use_count()<<endl; // 1

shared_ptr<Node> ptr2(ptr1);
cout<<"Use Count: "<<ptr2.use_count()<<endl; // 2
cout<<"Use Count: "<<ptr1.use_count()<<endl; // 2
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • But what if I want to create two shared_ptr(s) on the same object? For example on the given **node** object in the code? – Dee Jay Jan 12 '18 at 06:50
  • @DeeJay Answer revised. – songyuanyao Jan 12 '18 at 06:56
  • ptr2 and ptr1 are two different shared_ptrs pointing to the same Node object. I think you might have some kind of fundamental confusion about what the code is doing? – James Picone Jan 12 '18 at 06:56
  • @songyuanyao For ptr2, if I do: shared_ptrptr2(node); won't it work as well? – Dee Jay Jan 12 '18 at 07:08
  • @songyuanyao Why is the use_count() still 1 for ptr1 and ptr2 if both point to the same address? – Dee Jay Jan 12 '18 at 07:10
  • @DeeJay No, that's not how `shared_ptr` works. Think about the constructor of `shared_ptr` for `shared_ptrptr2(node);`, how does it check whether there're one or more `shared_ptr`s managing `node`? – songyuanyao Jan 12 '18 at 07:14
  • Yeah, it won't be able to. Unless it's initialized using another shared_ptr. – Dee Jay Jan 12 '18 at 07:23
  • @DeeJay Yes, this is what `shared_ptr` is supposed to do. – songyuanyao Jan 12 '18 at 07:24
  • So, node won't be **"freed"** until the shared_ptr(s) use_count becomes 0. – Dee Jay Jan 12 '18 at 07:30
  • @DeeJay Yes. And note if you have two separate `shared_ptr` pointing to the same pointer, the pointer will be *freed* twice at last. – songyuanyao Jan 12 '18 at 07:32
  • Won't that lead to a **SEGFAULT** for the second shared_ptr? – Dee Jay Jan 12 '18 at 07:35
  • @DeeJay It's [UB](http://en.cppreference.com/w/cpp/language/ub), anything is possible. It doesn't have to be a SEGFAULT. – songyuanyao Jan 12 '18 at 07:36
  • Then what's the use of having shared_ptr(s). I thought only the **last** shared_ptr would free the memory. If multiple shared_ptr(s) try freeing, then what's the use of even having them? – Dee Jay Jan 12 '18 at 07:39
  • @DeeJay `shared_ptr` is not omnipotent. You have to comply with its supposed usage. The samples showed in my answer are safe, when use_count shared by `shared_ptr`s become `0` the pointer will be destroyed only once, by the last `shared_ptr`. – songyuanyao Jan 12 '18 at 07:42
3

shared_ptr<>s own the object they are pointing too. That means you can't create a shared_ptr from a stack object. To create a shared_ptr use new or make_shared:

shared_ptr<Node> ptr1(new Node(42));
shared_ptr<Node> ptr2 = make_shared<Node>();
shared_ptr<Node> ptr3 = make_shared<Node>(99);

These are all separate objects and all have use_count()==1. If you want to copy them just do so:

shared_ptr<Node> copy1 = ptr1;

Now ptr1 and copy1 have use_count()==2.

Matthias Bäßler
  • 480
  • 1
  • 5
  • 11
2

there are multiple missunderstandings, but i will try to explain: in c++ objects are either stored on the stack or the heap. objects created on the stack will get destroyed and their memory gets released as soon as you leave the scope in which they are declared. this happens full automatically for you. for example:

int bla()
{
    int a = 1;
    int b = 2;
    int result = a+b;
    return result;
}

in this example the 3 intobjects will get created when the programm enter the function blaand will get destroyed when the function returns accordingly.

then you have the heap. you can create objects on the heap via new. this objects outlive the scope in which they got created, but you have to remember to destroy the objects via delete. because this is a common pitfall and source for memoryleaks the standard library provides helper classes (for example the shared_ptr which you already found) to overcome this issue. the idea is, that the shared_ptrobject will take the responsibility to delete the given object (which has to be on the heap(!) (yeah you can bypass this by a custom deleter, but thats a little bit more advanced than this explanation)) as soon as the shared_ptr itself gets destroyed. like the name suggests, the shared_ptr's intend is to be shared. to be shared you just copy the shared_ptr. the shared_ptr implementation has a custom copy constructor and copy assignment operator which will handle this by incrementing the use count and copying the address of the object that is currently handled. and this is the only way how a shared_ptr object will get to know wether there are other instances managing the same object or not. so to increase the use count you have to make a copy of the shared_ptr.

now let me explain what went wrong:

  1. your initial Node node = Node(10);is created on the stack (like the ints above). so there is no need to manage the lifetime manually. you created a shared_ptr, managing the address of this node. that is a misstake. as soon as you leave the scope the shared_ptr AND the node object itself(through the automatism on the stack) are going to delete the node object. and thats bad.

  2. lets imagine error 1 did not exists. so we have a second misstake: you are creating a second, seperate shared_ptr instead of copying from the first one. now both existing shared_ptr dont know about each other and each of them will try to delete the node object. so we have again a double delete.

lets pretend you want your Node object really be managed by a shared_ptr, you have to start with one shared_ptr and later copy from it as much as you want to share it. to create the first one you have 2 possiblilties:

std::shared_ptr<int> mysharedpointer (new int(ANYNUMBER));
auto mysharedpointer = std::make_shared<int>(ANYNUMBER);

usually you would prefer the second one, since it comes with a slight perfomance advantage and more safety when used in a function call context.

if you now want a second shared_ptr, sharing ownership with the first, you can do this by copying the first pointer:

auto mysecondsharedpointer = mysharedpointer;

and voila you have a copy associated with the shared ownership.

(this is all a bit simplified and i am sure i missed something. feel free to complete the answer)

phön
  • 1,215
  • 8
  • 20
  • What if I'm passing a shared_ptr to a function? Would the **normal** way work e.g. 'void (shared_ptra, shared_ptrb){}' . Would the shared_ptr's a & b increase the use_count? – Dee Jay Feb 06 '18 at 18:52
  • given your function signature (`void (shared_ptr a, shared_ptr b)`) both `shared_ptr`s are taken by value. this means they will get constructed(either copied or moved) from the arguments given to it. so yes, both, a and b, will increase the use count by one. if you pass the same `shared_ptr` to a and b, then of course the use_count will increase by 2 (since both share ownership to the same object) – phön Feb 07 '18 at 11:20
  • And would the use_count be decremented when the function returns? What if I don't want to increase the use_count in the function? Just use normal pointers? – Dee Jay Feb 07 '18 at 11:52
  • yes. if the function returns, the scope of the function exits and your `shared_ptr a` and `shared_ptr b` get destroyed. in the destructor of a `shared_ptr<>` the corresponding use_count will get decremented. – phön Feb 07 '18 at 12:26
  • if you want to avoid increasing the use_count, you can either: 1. move a shared_ptr when calling your function. this way the caller will lose ownership and the object gets deleted after the function 2. change your function signature to taking a reference of a shared_ptr. you should avoid this 3. change your function signature to take a reference of a object the shared_ptr is pointing to (in this case int): `void (int& a, int& b)`. on the callside you then have to dereference the shared_pointer 4. same as 3, but taking a non-owning raw pointer to int (`int*`). use if argument can be null – phön Feb 07 '18 at 12:35
  • One last question, is shared_ptr **thread-safe** . I mean, is the **use_count** increment and decrement operation atomic? In a multithreading scenario, is there a chance that **free** is called for an object by a thread, even though another thread may have a reference to said object. This could happen if the increment/decrement operations are not atomic. – Dee Jay Feb 07 '18 at 13:19
  • yes, the `shared_ptr` destructor is threadsafe across threads. no thread will "steal" your pointed to object away. BUT the pointed to object itself is not threadsafe. say you have a `shared_ptr` then you can not read the int in thread A and change it on thread B without any other synchronization – phön Feb 07 '18 at 13:42
  • Okay, so if thread-A has a reference to an object O, there is no chance that another thread-B would free said object O? By reference, I mean both thread A & B are using shared_ptr<>. – Dee Jay Feb 07 '18 at 13:57
  • thats correct. the last `shared_ptr<>` alive will clean up. ref counting is done in atomic fashion. no race condition ;-) if you want to read a little bit more: http://en.cppreference.com/w/cpp/memory/shared_ptr – phön Feb 07 '18 at 14:07
  • Cool, trying to solve a **double free** error, in a multithreading environment! :P – Dee Jay Feb 07 '18 at 14:12