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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With