I tried to understand the principle behind weak_ptr's implementation, especially about ref-counting.
The cppreference https://en.cppreference.com/w/cpp/memory/weak_ptr says weak_ptr works as an observer of shared_ptr, declaring weak_ptr doesn't change the use_count of original shared_ptr. Then my question is, how is weak_ptr implemented to know the use_count of original shared_ptr. I guess weak_ptr will hold a integer pointer to shared_ptr's use_count.
I had a quick test:
using namespace std;
int main()
{
auto sp = make_shared<int>(10);
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; // 1
sp.reset(new int{5});
cout << wp.use_count() << endl; // 0
auto swp = wp.lock();
cout << swp.get() << endl; // 0
cout << *swp << endl;
return 0;
}
As you could see the result of my program, as comments. So
(1) If weak_ptr doesn't hold an integer pointer to shared_ptr's use_count, then , how does it know the use_count() should change from 1 to 0 when I called sp.reset(new int[5])
. How could it know?
(2) If it holds such a pointer, when original shared_ptr came to end of lifecycle and destroyed, this pointer will point to a non-exist position! Dangling pointer!
Thus it seems to me a contradictory about how to implement weak_ptr.
Would you give some hints? Thanks a lot.
I tried to understand the principle behind weak_ptr's implementation
Take one implementation and observe it. For example on libstdc++ the class __weak_ptr
contains https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr_base.h#L1974 the pointer to memory and the counter:
template<typename _Tp, _Lock_policy _Lp>
class __weak_ptr {
...
_Tp* _M_ptr; // Contained pointer.
__weak_count<_Lp> _M_refcount; // Reference counter.
};
Where __weak_count
contains a pointer to the counter:
template<_Lock_policy _Lp>
class __weak_count
{
...
_Sp_counted_base<_Lp>* _M_pi;
};
Where _Sp_counted_base
actually holds the counters:
template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base
: public _Mutex_base<_Lp>
{
....
_Atomic_word _M_use_count; // #shared
_Atomic_word _M_weak_count; // #weak + (#shared != 0)
};
If it holds such a pointer, when original shared_ptr came to end of lifecycle and destroyed, this pointer will point to a non-exist position!
Not if the count itself is dynamically allocated! From https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr_base.h#L913 __shared_count
allocates _Sp_counted_ptr
on construction dynamically:
template<_Lock_policy _Lp>
class __shared_count
{
...
template<typename _Ptr>
explicit
__shared_count(_Ptr __p) : _M_pi(0)
{
...
_M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p);
...
}
...
_Sp_counted_base<_Lp>* _M_pi;
};
Where _Sp_counted_ptr
is dynamically allocated, and it holds the pointer to memory and the counters by inheriting from _Sp_counted_base
:
template<typename _Ptr, _Lock_policy _Lp>
// _Sp_counted_base has the count
class _Sp_counted_ptr final : public _Sp_counted_base<_Lp>
{
private:
_Ptr _M_ptr; // the pointer to memory
};