Handling dangling pointers
Earlier in this book we made the point that, when you delete a resource, you should set the pointer to nullptr and you should check a pointer before using it to see if it is nullptr. This is so that you do not call a pointer to memory for an object that has been deleted: a dangling pointer.
There are situations when a dangling pointer can occur by design. For example, a parent object may create child objects that have a back pointer to the parent so that the child has access to the parent. (An example of this is a window that creates child controls; it is often useful for the child controls to have access to the parent window.) The problem with using a shared pointer in this situation is that the parent will have a reference count on each child control and each child control has a reference count on the parent, and this creates a circular dependency.
Another example is if you have a container of observer objects with the intention of being able to inform each of these observer objects when an event occurs by calling a method on each one. Maintaining this list can be complicated, particularly if an observer object can be deleted, and hence you have to provide a means to remove the object from the container (where there will be a shared_ptr reference count) before you can completely delete the object. It becomes easier if your code can simply add a pointer to the object to the container in a way that does not maintain a reference count, but allows you to check when the pointer is used if the pointer is dangling or points to an existing object.
Such a pointer is called a weak pointer and the C++11 Standard Library provides a class called weak_ptr. You cannot use a weak_ptr object directly and there is no dereference operator.
Instead, you create a weak_ptr object from a shared_ptr object and, when you want to access the resource, you create a shared_ptr object from the weak_ptr object. This means that a weak_ptr object has the same raw pointer, and access to the same control block as the shared_ptr object, but it does not take part in reference counting.
Once created, the weak_ptr object will enable you to test whether the wrapper pointer is to an existing resource or to a resource that has been destroyed. There are two ways to do this: either call the member function expired or attempt to create a shared_ptr from the weak_ptr. If you are maintaining a collection of weak_ptr objects, you may decide to periodically iterate through the collection, call expired on each one, and if the method returns true, remove that object from the collection. Since the weak_ptr object has access to the control block created by the original shared_ptr object, it can test to see if the reference count is zero.
The second way to test to see if a weak_ptr object is dangling is to create a shared_ptr object from it. There are two options. You can create the shared_ptr object by passing the weak pointer to its constructor and if the pointer has expired, the constructor will throw a bad_weak_ptr exception. The other way is to call the lock method on the weak pointer and if the weak pointer has expired, then the shared_ptr object will be assigned to nullptr and you can test for this. These three ways are shown here:
shared_ptr<point> sp1 = make_shared<point>(1.0,1.0);
weak_ptr<point> wp(sp1);
// code that may call sp1.reset() or may not
if (!wp.expired()) { /* can use the resource */}
shared_ptr<point> sp2 = wp.lock();
if (sp2 != nullptr) { /* can use the resource */}
try
{
shared_ptr<point> sp3(wp);
// use the pointer
}
catch(bad_weak_ptr& e)
{
// dangling weak pointer
}
Since a weak pointer does not alter the reference count on a resource it means that you can use it for a back pointer to break the cyclic dependency (although, often it makes sense to use a raw pointer instead because a child object cannot exist without its parent object).