When is std::weak_ptr useful?

167,859

Solution 1

A good example would be a cache.

For recently accessed objects, you want to keep them in memory, so you hold a strong pointer to them. Periodically, you scan the cache and decide which objects have not been accessed recently. You don't need to keep those in memory, so you get rid of the strong pointer.

But what if that object is in use and some other code holds a strong pointer to it? If the cache gets rid of its only pointer to the object, it can never find it again. So the cache keeps a weak pointer to objects that it needs to find if they happen to stay in memory.

This is exactly what a weak pointer does -- it allows you to locate an object if it's still around, but doesn't keep it around if nothing else needs it.

Solution 2

std::weak_ptr is a very good way to solve the dangling pointer problem. By just using raw pointers it is impossible to know if the referenced data has been deallocated or not. Instead, by letting a std::shared_ptr manage the data, and supplying std::weak_ptr to users of the data, the users can check validity of the data by calling expired() or lock().

You could not do this with std::shared_ptr alone, because all std::shared_ptr instances share the ownership of the data which is not removed before all instances of std::shared_ptr are removed. Here is an example of how to check for dangling pointer using lock():

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << "weak1 value is " << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";
    
    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << "weak2 value is " << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}

Output

weak1 is expired
weak2 value is 5

Solution 3

Another answer, hopefully simpler. (for fellow googlers)

Suppose you have Team and Member objects.

Obviously it's a relationship : the Team object will have pointers to its Members. And it's likely that the members will also have a back pointer to their Team object.

Then you have a dependency cycle. If you use shared_ptr, objects will no longer be automatically freed when you abandon reference on them, because they reference each other in a cyclic way. This is a memory leak.

You break this by using weak_ptr. The "owner" typically use shared_ptr and the "owned" use a weak_ptr to its parent, and convert it temporarily to shared_ptr when it needs access to its parent.

Store a weak ptr :

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

then use it when needed

shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
  // yes, it may fail if the parent was freed since we stored weak_ptr
} else {
  // do stuff
}
// tempParentSharedPtr is released when it goes out of scope

Solution 4

Here's one example, given to me by @jleahy: Suppose you have a collection of tasks, executed asynchronously, and managed by an std::shared_ptr<Task>. You may want to do something with those tasks periodically, so a timer event may traverse a std::vector<std::weak_ptr<Task>> and give the tasks something to do. However, simultaneously a task may have concurrently decided that it is no longer needed and die. The timer can thus check whether the task is still alive by making a shared pointer from the weak pointer and using that shared pointer, provided it isn't null.

Solution 5

They are useful with Boost.Asio when you are not guaranteed that a target object still exists when an asynchronous handler is invoked. The trick is to bind a weak_ptr into the asynchonous handler object, using std::bind or lambda captures.

void MyClass::startTimer()
{
    std::weak_ptr<MyClass> weak = shared_from_this();
    timer_.async_wait( [weak](const boost::system::error_code& ec)
    {
        auto self = weak.lock();
        if (self)
        {
            self->handleTimeout();
        }
        else
        {
            std::cout << "Target object no longer exists!\n";
        }
    } );
}

This is a variant of the self = shared_from_this() idiom often seen in Boost.Asio examples, where a pending asynchronous handler will not prolong the lifetime of the target object, yet is still safe if the target object is deleted.

Share:
167,859
artm
Author by

artm

Perfection is the enemy of the good..

Updated on April 15, 2022

Comments

  • artm
    artm about 2 years

    I started studying smart pointers of C++11 and I don't see any useful use of std::weak_ptr. Can someone tell me when std::weak_ptr is useful/necessary?

  • Admin
    Admin almost 12 years
    So std::wake_ptr can point only where another pointer points and it points to nullptr when the object pointed is deleted/not pointed by any other pointers anymore?
  • David Schwartz
    David Schwartz almost 12 years
    @R.M.: Basically, yes. When you have a weak pointer, you can attempt to promote it to a strong pointer. If that object still exists (because at least one strong pointer to it exists still) that operation succeeds and gives you a strong pointer to it. If that object does not exist (because all strong pointers went away), then that operation fails (and typically you react by throwing away the weak pointer).
  • RoundPi
    RoundPi over 11 years
    :Sounds like a good example but can you please elaborate you example a bit more? I am thinking when a task is finished, it's should already been removed from the std::vector<std::weak_ptr<Task>> without a periodic check. So not sure if the std::vector<std::weak_ptr<>> is very helpful here.
  • uuu777
    uuu777 over 10 years
    Similar comment with queues: say you have objects and you queue them for some resource, objects could be deleted while waiting. So, if you queue weak_ptrs you do not have to bother with deleting entries from there queue. Weak_ptrs will be invalidated and then discarded when encoutnered.
  • Kerrek SB
    Kerrek SB over 10 years
    @zzz777: The logic that invalidates the objects may not even be aware of the existence of the queue or vector of observers. So the observer performs a separate loop over the weak pointers, acting on the ones that are still alive, and removing the dead ones from the container...
  • uuu777
    uuu777 over 10 years
    @KerekSB: yes and in case of queue you do not even have to a separate loop - then resource is available you discard expired weak_ptrs (if any) until you got valid one (if any).
  • paulm
    paulm over 10 years
    How is this a memory leak? If team is destructed it will destruct its members, thus the shared_ptr ref count will be 0 and also destructed?
  • Offirmo
    Offirmo over 10 years
    @paulm Team will not destroy "its" members. The whole point of shared_ptr is to share ownership, so no one has the particular responsibility to free the memory, it is freed automatically when no longer used. Unless there is a loop... You may have several teams sharing the same player (past teams ?). If the team object "owns" the members, then there is no need to use a shared_ptr to begin with.
  • paulm
    paulm over 10 years
    It won't destroy them but its shared_ptr will go out of scope with it, decrements the use_count, thus at this point use_count is 0 and so the shared_ptr will delete what its pointing to?
  • Offirmo
    Offirmo over 10 years
    @paulm You are right. But since, in this example, team is also a shared_ptr referenced by its "team members", when will it got destroyed ? What you are describing is a case where there is no loop.
  • Mazyod
    Mazyod almost 9 years
    It's not so bad, I would think. If a member can belong to many teams, using a reference won't work.
  • BlueTrin
    BlueTrin almost 9 years
    It is a bad use case but many people write this kind of code with circular shared_ptr.
  • curiousguy
    curiousguy about 8 years
    How can a weak reference deal with a circular dependency?
  • curiousguy
    curiousguy about 8 years
    "to break circular references" how?
  • The Vivandiere
    The Vivandiere almost 8 years
    While a strong pointer keeps an object alive, a weak_ptr can look at it... without mucking with the object's life time.
  • user
    user over 7 years
    Ok, it's as if if you locally set a (owning) pointer to null (delete memory), all other (weak) pointers to the same memory are also set to null
  • curiousguy
    curiousguy over 7 years
    @Offirmo "it is freed automatically when no longer used. Unless there is a loop..." no, it is freed when no longer used in every case; when a loop exist... a loop exist; every element of the list is a user. The objects are still used. There is no "loop defect" or loophole in ref counting. Loops work as specified: they cannot be destructed, period.
  • Jason C
    Jason C over 7 years
    Another example, which I've used a few times at least, is when implementing observers, it sometimes becomes convenient to have the subject maintain a list of weak pointers and do its own list cleanup. It saves a little bit of effort explicitly removing observers when they are deleted, and more significantly you don't have to have information about the subjects available when destroying observers which generally simplifies things a lot.
  • curiousguy
    curiousguy over 6 years
    You could also have the threads remove themselves from the collection, but that would create a dependency and require locking.
  • Shelby Moore III
    Shelby Moore III over 6 years
    @curiousguy, a child employs a weak reference to the parent, then the parent can be deallocated when there are no shared (strong) references pointing to it. Thus when accessing the parent via the child, the weak reference has to tested to see if the parent is still available. Alternatively to avoid that extra condition, a circular reference tracing mechanism (either mark-sweep or probing on refcount decrements, both of which have bad asymptotic performance) can break the circular shared references when the only shared references to the parent and child are from each other.
  • Shelby Moore III
    Shelby Moore III over 6 years
    The cache use case could work with a shared reference if the cache was able to test if the refcount is 1, thus knowing it has the only reference and it could release it reclaiming on demand. This would eliminate the need for weak references in this case. Weak references require a separate object for the count which has some performance and space drawbacks (although afaik that's the way shared_ptr always works in C++ apparently, so no choice).
  • curiousguy
    curiousguy over 6 years
    @ShelbyMooreIII "has to tested to see if the parent is still available" yes, and you have to be able to react correctly to the unavailable case! Which doesn't occur with a real (i.e. strong) ref. Which means weak ref is not a drop in replacement: it requires a change in the logic.
  • Shelby Moore III
    Shelby Moore III over 6 years
    @curiousguy you didn’t ask “How can a weak_ptr deal with a circular dependency with no change in program logic as a drop-in replacement for shared_ptr?” :-)
  • curiousguy
    curiousguy almost 6 years
    @ShelbyMooreIII If you write a ref counted smart ptr without a weak ptr, you need a separate object to store the count, unless the count is allocated with the managed object.
  • Orwellophile
    Orwellophile almost 6 years
    Why did it take so long to find this answer... P.S. you're not using your capture of this
  • Emile Cormier
    Emile Cormier almost 6 years
    @Orwellophile fixed. Force of habit when using the self = shared_from_this() idiom when the handler invokes methods within the same class.
  • rubenvb
    rubenvb almost 6 years
    Wait, what is wrong with the cache holding a shared_ptr and just removing it from its list when it should be cleared from memory? Any users will hold a shared_ptr all the same and the cached resource will be cleared as soon as all users are done with it.
  • rubenvb
    rubenvb almost 6 years
    ...Unless you're using the presence of a resource some sort of cancellation signal...
  • David Schwartz
    David Schwartz over 5 years
    @rubenvb What happens if new users of the cache look for the object in the cache? They won't find it even though it's in memory. If an object is used for long enough, you could wind up with numerous copies of it in memory, defeating the purpose of the cache. With the weak pointer, new cache users can find the object and cause the weak pointer to be promoted to a strong pointer, putting the object back in the cache and avoiding having unlimited copies in memory.
  • Rajesh
    Rajesh over 5 years
    Before calling "lock" to acquire shared_ptr, one can also call the method "expired" to ensure whether the object is alive or not
  • Helping Bean
    Helping Bean over 5 years
    @DavidSchwartz Could you clear up more on this? I couldn't get like when an object is not in the cache, how the new users of the cache can find the object in the cache and promote the weak pointer to strong pointer.
  • David Schwartz
    David Schwartz over 5 years
    There's an implementation that does this in the source code for rippled. If the object expires from the cache but there are existing strong pointers, it is demoted to being cached by a weak pointer. This permits the object to be destroyed if it's not being used but also found (and re-cached) if requested prior to being destroyed.
  • Oscar
    Oscar over 5 years
    I still don't see why the cache should use weak_ptr over shared_ptr. Is it an issue of overhead? In either case, you have to maintain some kind of last-access-time list for all the objects if you're going to remove disused items periodically. So why choose one type of pointer over the other?
  • David Schwartz
    David Schwartz over 5 years
    @Oscar How do you remove something from the cache without risking having an arbitrarily large number of copies of the item in memory then? If you keep the strong_ptr, the object never goes away. If you throw it away, then you can wind up with an unlimited number of copies of the object in memory as the next thread won't find it in the cache even if it's still in memory. Having the cache demote the strong_ptr to a weak_ptr solves the problem.
  • Oscar
    Oscar over 5 years
    Thanks for your reply. If you're going to iterate through the cache periodically to check for agedness, it can check the reference count on a shared_ptr just as it would check the validity of a weak_ptr, right? If something is expiring from the cache, it's going to be removed whether its reference is a weak_ptr or a sole shared_ptr, right? I don't see why using a different pointer type causes a proliferation of "unlimited copies." If the object is re-created, it'll be added back to the cache and won't expire for a while, no?
  • David Schwartz
    David Schwartz over 5 years
    @Oscar Say something is holding a shared_ptr to the object but it expires from the cache. What do you do? You don't want the cache to pin it in memory, so you can't keep a shared_ptr. But so long as the object is still in memory, you need to make sure code that checks the cache can find it. Other than having the cache hold a weak_ptr, what's your solution?
  • Oscar
    Oscar over 5 years
    If something is still using it (holding another shared_ptr to it), I wouldn't remove it from the cache. It's in use... so why would you deem it "expired?" As you say, if it's still in memory, other code should be able to find it. On our next pruning trip through the cache, we'll see if everyone's done with it, and if so it will then be expired.
  • David Schwartz
    David Schwartz about 5 years
    @Oscar Typically a cache has a well-defined operation that finds an object in the cache and extends it life. Your scheme would work but would allow any access to the object at all to reset the life of the object in the cache. So it would produce results with different semantics. I agree that this is a pretty fine difference and likely both schemes would work just as well under most circumstances.
  • Sahib Yar
    Sahib Yar about 5 years
    std::weak_ptr::lock creates a new std::shared_ptr that shares ownership of the managed object.
  • Franky
    Franky about 4 years
    I read your answer three times, but didn't understand what you accurately said.
  • Milan
    Milan over 3 years
    @DavidSchwartz, if possible, could you please elaborate on what did you mean by a strong pointer? Also, what did you mean by you scan the cache in "Periodically, you scan the cache and decide which objects have not been accessed recently. You don't need to keep those in memory, so you get rid of the strong pointer."? If I'm not wrong, OS scan the cache, right?
  • David Schwartz
    David Schwartz over 3 years
    @Milan The OS has no idea this cache exists, it's part of the application. A "strong pointer" is a pointer that represents ownership in the object and prevents it from being deleted.
  • Milan
    Milan over 3 years
    Thanks @DavidSchwartz for clarifying however I still have some more confusion/questions (sorry if they are stupid) 1. For recently accessed objects, we want to keep them in the cache rather than memory, right? 2. if some other application already has a strong pointe then why do we need weak_ptr? As another application has strong ptr to that object, the reference count won't be 0, right? 3. If you have good references for your answer that I can look into for an in-depth explanation then please do share.
  • David Schwartz
    David Schwartz over 3 years
    @Milan 1) The cache is part of memory. Keeping them in cache means keeping them in memory. 2) If we didn't have a weak pointer to the object, how would we find it if someone checked for it in the cache? It's in memory, we want the cache to be able to find it. 3) Not really, though you can look at the code. github.com/ripple/rippled/blob/develop/src/ripple/basics/…
  • Milan
    Milan about 2 years
    (scenario 1: having weak_ptr, instantiating a temporary shared_ptr through it, and then using that temp shared_ptr. (scenario 2: checking whether the main shared_ptr is valid or nullptr/NULL? Don't you think scenario 2 is way more straightforward (and probably a bit faster)? This thing confuses me a lot and it still makes me wonder why do we need weak_ptr? (except for tackling cyclic dependencies). Thank you so much in advance!
  • Milan
    Milan about 2 years
    (scenario 1: having weak_ptr, instantiating a temporary shared_ptr through it, and then using that temp shared_ptr. (scenario 2: checking whether the main shared_ptr is valid or nullptr/NULL? Don't you think scenario 2 is way more straightforward (and probably a bit faster)? This thing confuses me a lot and it still makes me wonder why do we need weak_ptr? (except for tackling cyclic dependencies). Thank you so much in advance!
  • Milan
    Milan about 2 years
    @curiousguy Rainer Grimm has done a pretty good job in explaining these circular references and how to break them using std::weak_ptr: "Back to Basics: Smart Pointers - Rainer Grimm - CppCon 2020" -- youtu.be/sQCSX7vmmKY?t=2496 (for some reason if the video starts from the beginning then skip to 41:36)