I know the object managed by a std::shared_ptr
is not delete
d by reset()
unless it is the only shared_ptr
that manages it at that point. I know that when there are multiple shared_ptr
s managing the same object, changes to the managed object’s value are reflected through all shared_ptr
s that point to it, while changes to any of these shared_ptr
’s value (not its managed object’s value) caused by reset()
ting it (i.e. changing the shared_ptr
from one that points to the original managed object to one that points to nothing or something else) does not change the other shared_ptr
s’ values (i.e. they all still point to the original managed object, and the original managed object still exists):
#include <memory>
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<shared_ptr<int>> vec{ make_shared<int>(5) };
shared_ptr<int> sptr(vec[0]);
++ *sptr;
cout << *vec[0] << endl; // 6
vec[0].reset();
vec.pop_back();
cout << *sptr << endl; // 6
}
But that logic is lost to me when using two levels of indirection. Given a class named Wrapper
and a shared_ptr<shared_ptr<Wrapper>>
and any number of other shared_ptr<shared_ptr<Wrapper>>
s initialized to the prior, why does this configuration allow a reset()
called on any inner shared_ptr
to effectively reset()
all other inner shared_ptr
s?
My guess is: the managed object of any of the outer shared_ptr
s is the inner shared_ptr
(not the Wrapper
) and changes to the value of the inner shared_ptr
(by reset()
ting the inner shared_ptr
, which changes the value of the inner shared_ptr
from one that points to a Wrapper
instance to one that points to nothing) is reflected throughout all outer shared_ptr
s, effectively causing all outer shared_ptr
s to lose indirect management over the Wrapper
instance, thereby deleting the Wrapper
instance.
But by the same logic, isn’t resetting one of the inner pointers only going to cause that particular inner pointer to lose management over the Wrapper
? Given that all the other outer pointers point to inner pointers of their own (i.e. the ones that were constructed with them), wouldn’t those outer ones continue to have indirect management over the Wrapper
, since resetting one inner pointer doesn’t change the Wrapper
’s value, which should still be accessible by the other inner pointers? It’s a paradox to me.
If resetting one inner pointer effectively resets all of them, then that means the inner pointers' use_count()
was 1
right before the reset()
. The only way I thought multiple shared_ptr
s can appear to manage the same object while keeping use_count()
at 1
would be through illusion: they manage different objects (i.e. objects at different addresses) that have the same value. I tested this by making an int
wrapper named Wrapper
, whose only data members are the wrapped int
and a static
instance_count
that keeps track of the number of Wrapper
instances that currently exist.
struct Wrapper {
Wrapper(int par = 0) : num(par) { ++instance_count; }
Wrapper(const Wrapper& src) : num(src.num) { ++instance_count; }
~Wrapper() { --instance_count; }
int num;
static int instance_count;
};
int Wrapper::instance_count = 0;
int main() {
shared_ptr<shared_ptr<Wrapper>> dual_ptr_1(
make_shared<shared_ptr<Wrapper>>(
make_shared<Wrapper>(Wrapper(5))
)
);
// - Output -
cout << Wrapper::instance_count << endl; // 1
shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(dual_ptr_1);
cout << Wrapper::instance_count << endl; // 1
cout << dual_ptr_1->use_count() << endl; // 1
cout << dual_ptr_2->use_count() << endl; // 1
cout << dual_ptr_1.use_count() << endl; // 2
cout << dual_ptr_2.use_count() << endl; // 2
// note that above, the '->' operator accesses
// inner ptr while '.' operator is for outer ptr
cout << (*dual_ptr_1)->num << endl; // 5
cout << (*dual_ptr_2)->num << endl; // 5
dual_ptr_2->reset();
cout << Wrapper::instance_count << endl; // 0
cout << dual_ptr_1->use_count() << endl; // 0
cout << dual_ptr_2->use_count() << endl; // 0
cout << dual_ptr_1.use_count() << endl; // 2
cout << dual_ptr_2.use_count() << endl; // 2
}
Apparently there were 2
inner pointers that point to 1
Wrapper
object; the inner pointers' use_count
was at most 1
(prior to destruction); the Wrapper
class's instance_count
was at most 1
(prior to destruction); and the indirectly managed Wrapper
object was accessible through both outer pointers (which means neither outer pointer got move-constructed by the other); and resetting one inner pointer effectively reset all of them; so I still don't understand the seeming paradox.
I'm also asking the same questions in this post about the case in which the above code has the inner shared_ptr
s replaced by unique_ptr
s, the inner make_shared
replaced by make_unique
, and the use_count()
commented out for the inner pointers (because unique_ptr
s lack that method), which gives the same output. That's a seeming paradox to me because the unique_ptr
s don't seem unique here.
Given a class named
Wrapper
and ashared_ptr<shared_ptr<Wrapper>>
and any number of othershared_ptr<shared_ptr<Wrapper>>
s initialized to the prior, why does this configuration allow areset()
called on any inner shared_ptr to effectivelyreset()
all other innershared_ptrs
?
There are no other inner shared_ptr
s, you have a single instance of the contained object, i.e.
dual_ptr_1
\
--> shared_ptr --> Wrapper
/
dual_ptr_2
And not
dual_ptr_1 --> shared_ptr
\
--> Wrapper
/
dual_ptr_2 --> shared_ptr
After your call to dual_ptr_2->reset();
this changes to
dual_ptr_1
\
--> shared_ptr --> (empty)
/
dual_ptr_2