std::weak_ptr in C++

The std::weak_ptr is part of the smart pointer family introduced in C++11 which holds a non-owning reference to an object managed by other std::shared_ptrs. It does not own the object, which means that the existence of a std::weak_ptr does not ensure the validity of the referred object. If all the std::shared_ptrs, owing the object, get destroyed, the managed object will also be destroyed even though one or more std::weak_ptrs are still referring to that object. But from the std::weak_ptr, we’ll be able to check whether the object really exists or not. We can access and use the object only if the object exists at that moment. The std::weak_ptr is a silent observer of the referred object.

Example of std::weak_ptr

#include <iostream>
#include <memory>

class C {
public:
  C() {
    std::cout << "Constructor called." << std::endl;
  }

  ~C() {
    std::cout << "Destructor called." << std::endl;
  }

  void func() {
    std::cout << "Func called." << std::endl;
  }

};

std::weak_ptr<C> wp;

void use_weak_ptr() {
    if (auto sp1 = wp.lock()) {
        std::cout << "C object is valid. " << std::endl;
        sp1->func();

        std::cout << "Use count: " << wp.use_count() << std::endl; 
    } else {
        std::cout << "C object is NOT valid. " << std::endl;
        std::cout << "Use count: " << wp.use_count() << std::endl;
    }
}

int main() {
    
    {
        auto sp = std::make_shared<C>();
        
        std::cout << "Use count before weak_ptr assignment: " << sp.use_count() << std::endl; 
        wp = sp;
        std::cout << "Use count after weak_ptr assignment: " << sp.use_count() << std::endl; 

        use_weak_ptr();
    }

    std::cout << std::endl;
    std::cout << "Checking the weak pointer outside the block..." << std::endl;
    use_weak_ptr();

    return 0;
}

In this example program, we have a class, C. We have prints in constructor and destructor of this class to understand where an object of type C is being created or destroyed. The member function, func(), is to demonstrate that an object can be used.

We declared a global std::weak_ptr of type C, std::weak_ptr wp. We declared it as global to access from multiple functions.

The use_weak_ptr() function tries to use the std::weak_ptr, wp. To access the actual object, first we need to create a std::shared_ptr out of the std::weak_ptr. The wp.lock() returns a std::shared_ptr if the actual object is alive at that moment. Otherwise it will return false. The returned std::shared::ptr is assigned to sp1. If we get a non-empty std::shared_ptr, the lifetime of the managed object gets extended. It will remain valid at least until sp1 is valid – does not go out of context.

So in the positive if-block, we accessed the object’s member function func().

If the else-block we just printed that the managed object is not valid.

In the main(), we created a C type std::shared_ptr, sp inside a block – auto sp = std::make_shared(). The std::shared_ptr is assigned to the std::weak_ptr, wp. The use_weak_ptr() is called both from inside the block where the std::shared_ptr created and out the block to see the difference.

Output

$ g++ -o test test.cpp
l$ ./test
Constructor called.
Use count before weak_ptr assignment: 1
Use count after weak_ptr assignment: 1
C object is valid.
Func called.
Use count: 2
Destructor called.

Checking the weak pointer outside the block...
C object is NOT valid.
Use count: 0

We can try to understand how std::weak_ptr behaves from the output above.

The actual object was created when the first std::shared_ptr, sp, was created – auto sp = std::make_shared(). So, we saw the print from the constructor.

All std::shared_ptr(s) managing a common object maintain a reference counter. We printed that counter before and after the std::weak_ptr assignment. From the output we can see that both values are 1. That confirms a weak_ptr does not increment the reference count. In other words, weak_ptr does not contribute to the lifespan of the management object. This is called a weak reference. Hence the name weak_ptr.

Then we called the use_weak_ptr() from the same block.

The code entered into the if-block. The print, “C object is valid.” confirms that. The wp.lock() call returned a shared_ptr, sp1. Now we have two share_ptr(s) – one in main() and another in use_weak_ptr(). That’s why the use count was printed as 2. Even though we have a global weak_ptr, wp, it did not contribute to the use count. In the if-block, we could call the member function, func().

When the use_weak_ptr() returns, the shared_ptr, sp1, is destroyed but the other one, sp, is still alive. But when the control came out of the first block of the main() function, the sp also went out of scope – hence destroyed.

So, outside that block, no shared_ptr is referring to the managed object. As a result the actual object also gets destroyed. The print from the destructor confirms that.

The global weak pointer, wp, is still alive but the referred object no longer exists.

So, when we called the use_weak_ptr() function again, the code entered into the else-block.

The wp.lock() returned false. That’s why we got the print – “C object is NOT valid.”. And the use count is also 0.

Benefits of std::weak_ptr

Safer than Raw Pointers

The whole purpose of smart pointers is to help the programmers manage memory safely and efficiently. std::weak_ptr also contributes to that.

When we use objects via raw pointers, there is no guarantee that the object will remain valid until we complete our usage. If the object is accessed via multiple variables (raw pointers or std::shared_ptrs) from multiple threads, there is a chance that the other threads destroy the object while we are accessing that via a raw pointer. That will lead to invalid memory access and eventual application crash.

But if we have a std::weak_ptr, we can get a std::shared_ptr out of that to access the object. We saw that in the above example. This new std::shared_ptr will ensure the validity of the object as long as we are accessing the object using that std::shared_ptr.

Breaking a std:shared_ptr Cyclic Reference

std:shared_ptrs are great at cleaning up memories at appropriate times to avoid invalid access and memory leak. But if we are not careful, a cyclic reference of shared_ptrs might be created. Two std::shared_ptrs will refer to each other and one object will not allow the other to be deleted. So, both the objects will remain forever in the application. It will lead to memory leak. The std::weak_ptr can be used in this case to break the cyclic reference to avoid the memory leak. We’ll discuss this in detail in the next article.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

Leave a Reply

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

0
0
0
0
0
0