We are going to write a program that is similar to the program we wrote in the unique_ptr recipe in order to get insights into the usage and principles of shared_ptr:
- At first, we just include the necessary headers and declare that we use the std namespace by default:
#include <iostream>
#include <memory>
using namespace std;
- Then we define a little helper class, which helps us see when instances of it are actually created and destroyed. We will manage instances of it with shared_ptr:
class Foo
{
public:
string name;
Foo(string n)
: name{move(n)}
{ cout << "CTOR " << name << 'n'; }
~Foo() { cout << "DTOR " << name << 'n'; }
};
- Next, we implement a function that takes a shared pointer to a Foo instance by value. Accepting shared pointers as arguments by value is more interesting than accepting them by reference because in this case, they need to be copied, which changes their internal reference counter, as we will see:
void f(shared_ptr<Foo> sp)
{
cout << "f: use counter at "
<< sp.use_count() << 'n';
}
- In the main function, we declare an empty shared pointer. By default constructing it, it is effectively a null pointer:
int main()
{
shared_ptr<Foo> fa;
- Next, we open another scope and instantiate two Foo objects. We create the first one using the new operator and then feed it into the constructor of a new shared_ptr. Then we create the second instance using make_shared<Foo>, which creates a Foo instance from the parameters we provide. This is the more elegant method because we can use auto type deduction and the object is already managed when we have the chance to access it for the first time. This is very similar to the unique_ptr recipe at this point:
{
cout << "Inner scope beginn";
shared_ptr<Foo> f1 {new Foo{"foo"}};
auto f2 (make_shared<Foo>("bar"));
- Since shared pointers can be shared, they need to track how many parties share them. This is done with an internal reference counter or use counter. We can print its value using use_count. The value is exactly 1 at this point because we did not copy it yet. We can copy f1 to fa, which increases the use counter to 2.
cout << "f1's use counter at " << f1.use_count() << 'n';
fa = f1;
cout << "f1's use counter at " << f1.use_count() << 'n';
- While we're leaving the scope, the shared pointers f1 and f2 are destroyed. The f1 variable's reference counter is decremented to 1 again, making fa the only owner of the Foo instance. While f2 is destroyed, its reference counter is decremented to 0. In this case, the shared_ptr pointer's destructor will call delete on this object, which disposes of it:
}
cout << "Back to outer scopen";
cout << fa.use_count() << 'n';
- Now, let's call the f function with our shared pointer in two different ways. At first, we call it naively by copying fa. The f function will then print that the reference counter has the value 2. In the second call to f, we move the pointer into the function. This makes f the only owner of the object:
cout << "first f() calln";
f(fa);
cout << "second f() calln";
f(move(fa));
- After f is returned, the Foo instance is destroyed immediately because we do not have ownership of it any longer. Therefore, all the objects are already destroyed when the main function returns:
cout << "end of main()n";
}
- Compiling and running the program yields the following output. In the beginning, we see "foo" and "bar" created. After we copied f1 (which points to "foo"), its reference counter was incremented to 2. While leaving the scope, "bar" is destroyed because the shared pointer to it being the subject of destruction is the only owner. The single 1 in the output is the reference count of fa, which is now the only owner of "foo". Afterward, we called function f twice. On the first call, we copied fa into it, which gave it a reference counter of 2 again. On the second call, we moved it into f, which did not alter its reference counter. Moreover, because f is the only owner of "foo" at this point, the object is destroyed immediately after f leaves the scope. This way, no other heap objects are destroyed after the last print line in main:
$ ./shared_ptr
Inner scope begin
CTOR foo
CTOR bar
f1's use counter at 1
f1's use counter at 2
DTOR bar
Back to outer scope
1
first f() call
f: use counter at 2
second f() call
f: use counter at 1
DTOR foo
end of main()