[c++11] When is std::weak_ptr useful?

When using pointers it's important to understand the different types of pointers available and when it makes sense to use each one. There are four types of pointers in two categories as follows:

  • Raw pointers:
    • Raw Pointer [ i.e. SomeClass* ptrToSomeClass = new SomeClass(); ]
  • Smart pointers:
    • Unique Pointers [ i.e.
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Shared Pointers [ i.e.
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Weak Pointers [ i.e.
      std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
      ]

Raw pointers (sometimes referred to as "legacy pointers", or "C pointers") provide 'bare-bones' pointer behavior and are a common source of bugs and memory leaks. Raw pointers provide no means for keeping track of ownership of the resource and developers must call 'delete' manually to ensure they are not creating a memory leak. This becomes difficult if the resource is shared as it can be challenging to know whether any objects are still pointing to the resource. For these reasons, raw pointers should generally be avoided and only used in performance-critical sections of the code with limited scope.

Unique pointers are a basic smart pointer that 'owns' the underlying raw pointer to the resource and is responsible for calling delete and freeing the allocated memory once the object that 'owns' the unique pointer goes out of scope. The name 'unique' refers to the fact that only one object may 'own' the unique pointer at a given point in time. Ownership may be transferred to another object via the move command, but a unique pointer can never be copied or shared. For these reasons, unique pointers are a good alternative to raw pointers in the case that only one object needs the pointer at a given time, and this alleviates the developer from the need to free memory at the end of the owning object's lifecycle.

Shared pointers are another type of smart pointer that are similar to unique pointers, but allow for many objects to have ownership over the shared pointer. Like unique pointer, shared pointers are responsible for freeing the allocated memory once all objects are done pointing to the resource. It accomplishes this with a technique called reference counting. Each time a new object takes ownership of the shared pointer the reference count is incremented by one. Similarly, when an object goes out of scope or stops pointing to the resource, the reference count is decremented by one. When the reference count reaches zero, the allocated memory is freed. For these reasons, shared pointers are a very powerful type of smart pointer that should be used anytime multiple objects need to point to the same resource.

Finally, weak pointers are another type of smart pointer that, rather than pointing to a resource directly, they point to another pointer (weak or shared). Weak pointers can't access an object directly, but they can tell whether the object still exists or if it has expired. A weak pointer can be temporarily converted to a shared pointer to access the pointed-to object (provided it still exists). To illustrate, consider the following example:

  • You are busy and have overlapping meetings: Meeting A and Meeting B
  • You decide to go to Meeting A and your co-worker goes to Meeting B
  • You tell your co-worker that if Meeting B is still going after Meeting A ends, you will join
  • The following two scenarios could play out:
    • Meeting A ends and Meeting B is still going, so you join
    • Meeting A ends and Meeting B has also ended, so you can't join

In the example, you have a weak pointer to Meeting B. You are not an "owner" in Meeting B so it can end without you, and you do not know whether it ended or not unless you check. If it hasn't ended, you can join and participate, otherwise, you cannot. This is different than having a shared pointer to Meeting B because you would then be an "owner" in both Meeting A and Meeting B (participating in both at the same time).

The example illustrates how a weak pointer works and is useful when an object needs to be an outside observer, but does not want the responsibility of sharing ownership. This is particularly useful in the scenario that two objects need to point to each other (a.k.a. a circular reference). With shared pointers, neither object can be released because they are still 'strongly' pointed to by the other object. When one of the pointers is a weak pointer, the object holding the weak pointer can still access the other object when needed, provided it still exists.