简体   繁体   中英

Role of weak_ptr in shared_ptr

I understand how a shared_ptr works except for the role of the weak_ptr. I understand its there to detect circular references when the reference count isn't zero, but beyond this I don't understand just exactly how it does this. What does it do?

See also: When is std::weak_ptr useful? for why and How does weak_ptr work? for how.

I'll provide an example of how I've seen it used, though the sample code I whipped up is a bit convoluted so bear with me:

#include <vector>
#include <memory>
#include <ostream>

int main()
{
  // Fill container with values 1-50. This container OWNS these values.
  std::vector<std::shared_ptr<int>> owning_container;
  for(int i = 1; i <= 50; ++i)
  {
    owning_container.emplace_back(std::make_shared<int>(i));
  }

  // Create a sepearate container that references all owned values that are multiples of 5.
  std::vector<std::weak_ptr<int>> referencing_container;
  for(std::shared_ptr<int> const& i : owning_container)
  {
    if((*i) % 5 == 0)
    {
      // Make weak_ptr that references shared_ptr
      referencing_container.emplace_back(i);
    }
  }

  // Go through the owned values and delete all that are multiples of 10.
  for(auto it = owning_container.begin(); it != owning_container.end();)
  {
    std::shared_ptr<int> const& i = *it;
    if(*i % 10 == 0)
    {
      it = owning_container.erase(it);
    }
    else
    {
      ++it;
    }
  }

  // Now go through the referencing container and print out all values.
  // If we were dealing with raw pointers in both containers here we would access deleted memory,
  //   since some of the the actual resources (in owning_container) that referencing_container
  //   references have been removed.
  for(std::weak_ptr<int> const& i_ref : referencing_container)
  {
    // Check if the shared resource still exists before using it (purpose of weak_ptr)
    std::shared_ptr<int> i = i_ref.lock();
    if(i)
    {
      std::cout << *i << std::endl;
    }
  }

  return 0;
}

Here we have a container that contains some shared resource - ints in this case - ( shared_ptr ), that another container needs to reference ( weak_ptr ). The referencing does not own the resource, it only needs to be able to access it if it exists . In order to tell if the resource being referenced is still alive, we convert the weak_ptr to a shared_ptr using weak_ptr::lock() . Those resources that still exist will have a valid shared_ptr returned by lock() . Those that no longer exist will return a null shared_ptr . shared_ptr has an operator bool() that we can use to check if it is null or not before attempting to use it.

A less convoluted scenario might be if you were making a game where every object in the game was represented by a game_object . Say you have some kind of seeking logic for an enemy which requires a target game_object to seek to. Using the above, you could have the enemy hold onto a weak_ptr<game_object> . It doesn't own this game_object . If something else kills its target, its target should die; not hang in some limbo state which would happen if the enemy held a shared_ptr instead. This way if the enemy's target is still alive (which it can check by locking the weak_ptr ), it can execute the seeking logic; otherwise it can find a new target. The "owner" of the game_object could be some sort of game_world class - this would have a container of shared_ptr<game_object> . When the enemy needs a new target it could search through this container and create its weak_ptr from the game_world 's shared_ptr .

Weak pointers don't claim ownership of a resource, but only refer to it. Thus, they don't allow you to operate on a resource in any way except claiming the ownership once again (with the weak_ptr::lock() method). IMHO, the most frequent real-life situations where such a behaviour is desires are cyclic dependencies and (less frequently) caching.

Cyclic dependencies made with shared pointers only are de facto memory leaks, because mutual reference counts of the pointers will never be less than 1: if nothing else owns A, then B still does, and vice versa. A weak pointer doesn't 'detect' this situation. It just won't let the trouble in, simply by breaking the loop of ownership. You might still 'connect the ends of the chain' by locking the weak pointer, but none of the shared pointers that you might obtain via the weak one will persist.

The problem with caching is, yet again, that a cache usually shouldn't affect lifetime of the cached content, it is not a duty of the cache. But, if the cache would hold shared pointers, then you wouldn't be able to terminate the lifetime of a cached object without having to talk to the cache, which is often inconvenient.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM