Beyond Ownership: Understanding the Role of std::weak_ptr in Modern C++

Written by on May 4, 2025 in C++, Programming with 0 Comments

Smart Pointers in C++ are elegant abstractions for managing dynamic memory safely, avoiding dangling pointers and preventing leaks. While std::unique_ptr and std::shared_ptr are well understood and widely used, std::weak_ptr often demands a deeper dive to use correctly. In this article, I will attempt to explain what it is and where it is useful.

Some basics first. unique_ptr is useful when we need exclusive ownership of a resource. It cannot be copied, only moved. On the otherhand, we use shared_ptr when we need shared ownership. Internally it uses reference counting to keep track of shared references.

weak_ptr is different from the above two. At its core, it is a smart pointer that holds a non-owning (“weak”) reference to an object managed by shared_ptr. The key point to note is that it doesn’t “own” anything, but can observe whether the object still exists.

Let us start with a simple example. Assume we have two classes that need to reference each other. See the code below:

Circular Reference Example

Circular Reference Example

In the above case, both are using shared_ptr to reference each other. The problem with this is that it results in a “cyclic reference” and hence the objects will not be destroyed! This is confirmed by the program output:

Program Output

Program Output

You can see that the destructors are not called.

The correct solution is to use a weak_ptr in one of the classes, say “StructB”. The changed version is here:

Using weak_ptr Instead

Using weak_ptr Instead

The first thing to note is that a weak_ptr is created from a shared_ptr, not from a raw pointer. In this case, since the weak_ptr doesn’t “own” the reference, both the objects are correctly destroyed. See the output:

Output After Correction

Output After Correction

The following example shows other interesting aspects of weak_ptr:

Other Features of weak_ptr

Other Features of weak_ptr

Here are the key points to understand from the above example:

1) It is OK to initialize a week_ptr with another weak_ptr, but this doesn’t change the reference count of the managed object.

2) The “use_count()” function returns the reference count of the shared_ptr referenced by this weak_ptr. 

3) The “lock()” method is required to access the managed object. This will be empty if the shared_ptr has been destructed.

4) The “expired” method returns “true” if the managed object has been deleted, else it returns “false”.

Let us conclude this article with an example of the popular “Observer” pattern:

Observer Pattern

Observer Pattern

The EventDispatcher class handles registering and notifying multiple listerners. To keep things simple, the “listener” is implemented as a “function” object, but it could be a more feature-rich class. Note how the “notify()” method removes deleted listeners from the queue.

When we run the program, this is the output:

Observer Pattern Output

Observer Pattern Output

In summary, here are the key properties of weak_ptr:

1. Non-ownership: It doesn’t own the resource it references and hence doesn’t affect its lifetime

2. Observation capability: It can observe whether the object still exists

3. Convertibility: It can be converted to a shared_ptr to access the object (if it still exists) 

4. Safety: It helps in preventing dangling pointer issues by providing safe access mechanisms

Hope you found the article useful. You can download the source from here. The code was tested in Visual Studio Professional (64 bit) ver 17.13.5. 

Have a great week ahead!

Tags: , , , ,

About the Author

About the Author: .

Subscribe

If you enjoyed this article, subscribe now to receive more just like it.

Subscribe via RSS Feed

Leave a Reply

Your email address will not be published. Required fields are marked *

Top