We will implement a program that maintains objects with shared_ptr instances, and then, we mix in weak_ptr to see how this changes the behavior of smart pointer memory handling:
- At first, we include the necessary headers and declare that we use the std namespace by default:
#include <iostream>
#include <iomanip>
#include <memory>
using namespace std;
- Next, we implement a class that prints a message in its destructor implementation. This way, we can simply check when an item is actually destroyed later in the program output:
struct Foo {
int value;
Foo(int i) : value{i} {}
~Foo() { cout << "DTOR Foo " << value << 'n'; }
};
- Let's also implement a function that prints information about a weak pointer, so we can print a weak pointer's state at different points of our program. The expired function of weak_ptr tells us if the object it points to still really exists, because holding a weak pointer to an object does not prolong its lifetime! The use_count counter tells us how many shared_ptr instances are currently pointing to the object in question:
void weak_ptr_info(const weak_ptr<Foo> &p)
{
cout << "---------" << boolalpha
<< "nexpired: " << p.expired()
<< "nuse_count: " << p.use_count()
<< "ncontent: ";
- If we want to access the actual object, we need to call the lock function. It returns us a shared pointer to the object. In case the object does not exist any longer, the shared pointer we got from it is effectively a null pointer. We need to check that, and then we can access it:
if (const auto sp (p.lock()); sp) {
cout << sp->value << 'n';
} else {
cout << "<null>n";
}
}
- Let's instantiate an empty weak pointer in the main function and print its content which is, of course, empty at first:
int main()
{
weak_ptr<Foo> weak_foo;
weak_ptr_info(weak_foo);
- In a new scope, we instantiate a new shared pointer with a fresh instance of the Foo class. Then we copy it to the weak pointer. Note that this will not increment the reference count of the shared pointer. The reference counter is 1 because only one shared pointer owns it:
{
auto shared_foo (make_shared<Foo>(1337));
weak_foo = shared_foo;
- Let's call the weak pointer function before we leave the scope and, again, after we leave the scope. The Foo instance should be destroyed immediately, although a weak pointer points to it:
weak_ptr_info(weak_foo);
}
weak_ptr_info(weak_foo);
}
- Compiling and running the program yields us three times the output of the weak_ptr_info function. In the first call, the weak pointer is empty. In the second call, it already points to the Foo instance we created and is able to dereference it after locking it. Before the third call, we leave the inner scope, which triggers the destructor of the Foo instance, as we expected. Afterward, it is not possible to get at the content of the deleted Foo item via the weak pointer any longer, and the weak pointer correctly recognizes that it has expired:
$ ./weak_ptr
---------
expired: true
use_count: 0
content: <null>
---------
expired: false
use_count: 1
content: 1337
DTOR Foo 1337
---------
expired: true
use_count: 0
content: <null>