379

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?

1

15 Answers 15

406

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
4
  • 1
    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
    – user
    Commented Dec 24, 2016 at 19:47
  • 3
    std::weak_ptr::lock creates a new std::shared_ptr that shares ownership of the managed object.
    – Sahib Yar
    Commented May 14, 2019 at 12:19
  • 1
    Understood the concept. But what is practical use case? any example?
    – Fennekin
    Commented Jan 24, 2023 at 15:33
  • @Fennekin The use cases of weak_ptr are almost the same as those of weak references in references based languages like Java, that is, extremely few!
    – curiousguy
    Commented Feb 26, 2023 at 3:38
301

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.

22
  • 18
    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?
    – user1434698
    Commented Aug 19, 2012 at 23:16
  • 41
    @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). Commented Aug 19, 2012 at 23:38
  • 26
    While a strong pointer keeps an object alive, a weak_ptr can look at it... without mucking with the object's life time. Commented Sep 19, 2016 at 21:10
  • 5
    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.
    – Jason C
    Commented Jan 26, 2017 at 17:38
  • 6
    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
    Commented Aug 9, 2018 at 14:44
182

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
10
  • 2
    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?
    – paulm
    Commented Mar 16, 2014 at 1:25
  • 10
    @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.
    – Offirmo
    Commented Mar 16, 2014 at 9:53
  • 2
    @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.
    – Offirmo
    Commented Mar 16, 2014 at 14:59
  • 15
    It's not so bad, I would think. If a member can belong to many teams, using a reference won't work.
    – Mazyod
    Commented Aug 9, 2015 at 11:45
  • 2
    Before calling "lock" to acquire shared_ptr, one can also call the method "expired" to ensure whether the object is alive or not
    – Rajesh
    Commented Jan 7, 2019 at 10:12
33

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.

6
  • 7
    :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.
    – RoundPi
    Commented Oct 16, 2012 at 21:26
  • 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.
    – uuu777
    Commented Jan 22, 2014 at 20:43
  • 2
    @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...
    – Kerrek SB
    Commented Jan 22, 2014 at 20:47
  • 1
    @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).
    – uuu777
    Commented Jan 22, 2014 at 21:19
  • 1
    You could also have the threads remove themselves from the collection, but that would create a dependency and require locking.
    – curiousguy
    Commented Dec 8, 2017 at 23:30
26

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.

2
  • 2
    Raw pointers should not be avoided. It should be avoided to own an object through a raw pointer, but in cases where you reference something without owning it, a raw pointer is very useful. For example an object pointing back at the parent. Commented Jul 21, 2023 at 23:54
  • @CrisLuengo, or very common when you need usage not ownership from a unique_ptr p : p.get() :)
    – LRDPRDX
    Commented Jun 20 at 4:42
24

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.

2
  • Why did it take so long to find this answer... P.S. you're not using your capture of this Commented Aug 8, 2018 at 14:10
  • @Orwellophile fixed. Force of habit when using the self = shared_from_this() idiom when the handler invokes methods within the same class. Commented Aug 9, 2018 at 14:28
20

shared_ptr : holds the real object.

weak_ptr : uses lock to connect to the real owner or returns a NULL shared_ptr otherwise.

weak ptr

Roughly speaking, weak_ptr role is similar to the role of housing agency. Without agents, to get a house on rent we may have to check random houses in the city. The agents make sure that we visit only those houses which are still accessible and available for rent.

2
  • (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
    Commented Apr 13, 2022 at 18:20
  • 1
    @Milan As far as I'm concerned, std::weak_ptr will guarantee that lock is done atomically. So scenario 2 may miss some cases: after you check it's valid, you gonna assign it to another; What if the thread switches and the assignment is intercepted, and in between the original std::shared_ptr empty its resource? You will get an empty pointer in the assignment but regard it as valid in your code block, which may cause strange crashes. Scenario 1 will not since all operations are down (equivalent to) "at the same time".
    – o_oTurtle
    Commented Jul 21, 2022 at 8:45
18

weak_ptr is also good to check the correct deletion of an object - especially in unit tests. Typical use case might look like this:

std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());
9

Apart from the other already mentioned valid use cases std::weak_ptr is an awesome tool in a multithreaded environment, because

  • It doesn't own the object and so can't hinder deletion in a different thread
  • std::shared_ptr in conjunction with std::weak_ptr is safe against dangling pointers - in opposite to std::unique_ptr in conjunction with raw pointers
  • std::weak_ptr::lock() is an atomic operation (see also About thread-safety of weak_ptr)

Consider a task to load all images of a directory (~10.000) simultaneously into memory (e.g. as a thumbnail cache). Obviously the best way to do this is a control thread, which handles and manages the images, and multiple worker threads, which load the images. Now this is an easy task. Here's a very simplified implementation (join() etc is omitted, the threads would have to be handled differently in a real implementation etc)

// a simplified class to hold the thumbnail and data
struct ImageData {
  std::string path;
  std::unique_ptr<YourFavoriteImageLibData> image;
};

// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageData : imagesToLoad )
     imageData->image = YourFavoriteImageLib::load( imageData->path );
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas = 
        splitImageDatas( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

But it becomes much more complicated, if you want to interrupt the loading of the images, e.g. because the user has chosen a different directory. Or even if you want to destroy the manager.

You'd need thread communication and have to stop all loader threads, before you may change your m_imageDatas field. Otherwise the loaders would carry on loading until all images are done - even if they are already obsolete. In the simplified example, that wouldn't be too hard, but in a real environment things can be much more complicated.

The threads would probably be part of a thread pool used by multiple managers, of which some are being stopped, and some aren't etc. The simple parameter imagesToLoad would be a locked queue, into which those managers push their image requests from different control threads with the readers popping the requests - in an arbitrary order - at the other end. And so the communication becomes difficult, slow and error-prone. A very elegant way to avoid any additional communication in such cases is to use std::shared_ptr in conjunction with std::weak_ptr.

// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageDataWeak : imagesToLoad ) {
     std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
     if( !imageData )
        continue;
     imageData->image = YourFavoriteImageLib::load( imageData->path );
   }
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas = 
        splitImageDatasToWeak( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

This implementation is nearly as easy as the first one, doesn't need any additional thread communication, and could be part of a thread pool/queue in a real implementation. Since the expired images are skipped, and non-expired images are processed, the threads never would have to be stopped during normal operation. You could always safely change the path or destroy your managers, since the reader fn checks, if the owning pointer isn't expired.

0
9

I see a lot of interesting answers that explain reference counting etc., but I am missing a simple example that demonstrates how you prevent memory leak using weak_ptr. In first example I use shared_ptr in cyclically referenced classes. When the classes go out of scope they are NOT destroyed.

#include<iostream>
#include<memory>
using namespace std;

class B;

class A
{
public:
    shared_ptr<B>bptr;
    A() {
        cout << "A created" << endl;
    }
    ~A() {
        cout << "A destroyed" << endl;
    }
};

class B
{
public:
    shared_ptr<A>aptr;
    B() {
        cout << "B created" << endl;
    }
    ~B() {
        cout << "B destroyed" << endl;
    }
};

int main()
{
    {
        shared_ptr<A> a = make_shared<A>();
        shared_ptr<B> b = make_shared<B>();
        a->bptr = b;
        b->aptr = a;
    }
  // put breakpoint here
}

If you run the code snippet you will see as classes are created, but not destroyed:

A created
B created

Now we change shared_ptr's to weak_ptr:

class B;
class A
{
public:
    weak_ptr<B>bptr;

    A() {
        cout << "A created" << endl;
    }
    ~A() {
        cout << "A destroyed" << endl;
    }
};

class B
{
public:
    weak_ptr<A>aptr;

    B() {
        cout << "B created" << endl;
    }
    ~B() {
        cout << "B destroyed" << endl;
    }
};

    int main()
    {
        {
            shared_ptr<A> a = make_shared<A>();
            shared_ptr<B> b = make_shared<B>();
            a->bptr = b;
            b->aptr = a;
        }
      // put breakpoint here
    }

This time, when using weak_ptr we see proper class destruction:

A created
B created
B destroyed
A destroyed
1
  • 2
    A working example like this, is worth a thousand words. Thanks.
    – daparic
    Commented Dec 15, 2022 at 17:51
6

I see std::weak_ptr<T> as a handle to a std::shared_ptr<T>: It allows me to get the std::shared_ptr<T> if it still exists, but it will not extend its lifetime. There are several scenarios when such point of view is useful:

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

Another important scenario is to break cycles in data structures.

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

Herb Sutter has an excellent talk that explains the best use of language features (in this case smart pointers) to ensure Leak Freedom by Default (meaning: everything clicks in place by construction; you can hardly screw it up). It is a must watch.

2

http://en.cppreference.com/w/cpp/memory/weak_ptr std::weak_ptr is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by std::shared_ptr. It must be converted to std::shared_ptr in order to access the referenced object.

std::weak_ptr models temporary ownership: when an object needs to be accessed only if it exists, and it may be deleted at any time by someone else, std::weak_ptr is used to track the object, and it is converted to std::shared_ptr to assume temporary ownership. If the original std::shared_ptr is destroyed at this time, the object's lifetime is extended until the temporary std::shared_ptr is destroyed as well.

In addition, std::weak_ptr is used to break circular references of std::shared_ptr.

3
  • "to break circular references" how?
    – curiousguy
    Commented Jun 14, 2016 at 23:10
  • @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)
    – Milan
    Commented Apr 13, 2022 at 18:55
  • This is just a copy/paste of the link, which people searching could already find themselves. It adds nothing. Commented Mar 24, 2023 at 13:19
2

There is a drawback of shared pointer: shared_pointer can't handle the parent-child cycle dependency. Means if the parent class uses the object of child class using a shared pointer, in the same file if child class uses the object of the parent class. The shared pointer will be failed to destruct all objects, even shared pointer is not at all calling the destructor in cycle dependency scenario. basically shared pointer doesn't support the reference count mechanism.

This drawback we can overcome using weak_pointer.

4
  • How can a weak reference deal with a circular dependency?
    – curiousguy
    Commented Jun 11, 2016 at 0:45
  • 1
    @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. Commented Jan 13, 2018 at 20:14
  • @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.
    – curiousguy
    Commented Jan 17, 2018 at 23:20
  • 2
    @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?” :-) Commented Jan 20, 2018 at 6:02
2

When we does not want to own the object:

Ex:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

In the above class wPtr1 does not own the resource pointed by wPtr1. If the resource is got deleted then wPtr1 is expired.

To avoid circular dependency:

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

Now if we make the shared_ptr of the class B and A, the use_count of the both pointer is two.

When the shared_ptr goes out od scope the count still remains 1 and hence the A and B object does not gets deleted.

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

output:

A()
B()

As we can see from the output that A and B pointer are never deleted and hence memory leak.

To avoid such issue just use weak_ptr in class A instead of shared_ptr which makes more sense.

0
2

Inspired by @offirmo's response I wrote this code and then ran the visual studio diagnostic tool:

#include <iostream>
#include <vector>
#include <memory>

using namespace std;

struct Member;
struct Team;

struct Member {
    int x = 0;

    Member(int xArg) {
        x = xArg;
    }

    shared_ptr<Team> teamPointer;
};

struct Team {
    vector<shared_ptr<Member>> members;
};

void foo() {
    auto t1 = make_shared<Team>();
    for (int i = 0; i < 1000000; i++) {
        t1->members.push_back(make_shared<Member>(i));
        t1->members.back()->teamPointer = t1;
    }
}

int main() {
    foo();

    while (1);

    return 0;
}

When the member pointer to the team is shared_ptr teamPointer the memory is not free after foo() is done, i.e. it stays at around 150 MB.

But if it's changed to weak_ptr teamPointer in the diagnostic tool you'll see a peak and then memory usage returns to about 2MB.