c++countreferenceshared-ptrweak-ptr

How does std::weak_ptr store its "use_count" information?


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.


Solution

  • 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
        };