Home > OS >  How to prove c 11 make_shared() can keep shared_ptr's control block alive even after its dtor?
How to prove c 11 make_shared() can keep shared_ptr's control block alive even after its dtor?

Time:07-31

My question is: is there a case that share_ptr ref count is 0, while weak_ptr ref count is not 0?

Difference in make_shared and normal shared_ptr in C

Referr this thread, showing that if a shared_ptr is created by make_shared and there's weak_ptr, its control block will be alive until both shared_ptr and weak_ptr's ref count gets 0. It says:

There must be a way for weak_ptrs to determine if the managed object is still valid (eg. for lock). They do this by checking the number of shared_ptrs that own the managed object, which is stored in the control block. The result is that the control blocks are alive until the shared_ptr count and the weak_ptr count both hit 0.


I had a quick test. I hope the shared_ptr created by make_shared, if there's weak_ptr pointing to it, its control block will still hold sth after shared_ptr is destructed.

#include<memory>
#include<iostream>
using namespace std;
struct My {
    int m_i;
    My(int i) : m_i(i) { cout << "My ctor:" << m_i << '\n';}
    ~My() { cout << "My ctor:" << m_i << '\n';}
};
weak_ptr<My> wp1, wp2;
int main() {
    {
        auto sp1 = shared_ptr<My>(new My(30));
        wp1 = weak_ptr<My>(sp1);
    }
    cout<< wp1.use_count() << endl;
    {
        auto sp2 = make_shared<My>(40);
        wp2 = weak_ptr<My>(sp2);
    }
    cout<< wp2.use_count() << endl;
    return 0;
}

Both use_count will print 0, my program didn't seems to show whether the control blocks is alive or not.

So, how can we prove that The result is that the control blocks are alive until both shared_ptr and weak_ptr's ref count gets 0?

Any sample code that could test/prove this theory?

CodePudding user response:

This is not a "theory" that needs to be "proven". It is true by fiat; the standard requires that weak_ptr know when all shared_ptrs they are linked to are destroyed. This is necessitated by the requirements of weak_ptr::lock. And the only viable way to implement that (without also violating thread safety guarantees, or providing them in incredibly bad ways) is to have a block of memory that is owned by both the weak and shared pointers which is only disposed of when all have been destroyed.

If you wish to see this demonstrated, you can pass the shared_ptr's constructor a user-defined allocator type. shared_ptr will use this to allocate the control block for the shared_ptr. The control block will only be deleted when the last shared or weak pointer is destroyed. You can do the same for allocate_shared.

CodePudding user response:

Your code couldn't prove, because sp1,sp2 are local variables, they will be released when they out of range. And you could use hook or froce jump to show the result you want to see, but it's not easy. We could read the source code of weak_ptr, so we can know about what really does, the souce code of weak_ptr is too much, we just view the key segment:

  // The actual weak_ptr, with forwarding constructors and
  // assignment operators.
  template<typename _Tp>
    class weak_ptr
    : public __weak_ptr<_Tp>
    {
     
    };

From the code, we could see that most work are done by the father class __weak_ptr, that's view the code of __weak_ptr:

template<typename _Tp, _Lock_policy _Lp>
    class __weak_ptr
    {
    public:
      
      
      template<typename _Tp1>
        __weak_ptr(const __shared_ptr<_Tp1, _Lp>& __r)
    : _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount) // never throws
        { __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>) }
 
 
      long
      use_count() const // never throws
      { return _M_refcount._M_get_use_count(); }
 
    
      _Tp*            _M_ptr;         // Contained pointer.
      __weak_count<_Lp>  _M_refcount;    // Reference counter.
    };

When we call the use_count() of weak_ptr, we call _M_refcount._M_get_use_count(), _M_refcount was assigned in the constructor of class __weak_ptr , in the assign list we could see _M_refcount(__r._M_refcount), so we should know what really does in the constructor of __weak_count:

template<_Lock_policy _Lp>
    class __weak_count
    {
    public:
      
      __weak_count(const __shared_count<_Lp>& __r)
      : _M_pi(__r._M_pi) // nothrow
      {
    if (_M_pi != 0)
      _M_pi->_M_weak_add_ref();
      }
      
    
      long
      _M_get_use_count() const // nothrow
      { return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0; }
 
    
    private:
      friend class __shared_count<_Lp>;
 
      _Sp_counted_base<_Lp>*  _M_pi;
    };

In the constructor of class __weak_count the member _M_pi is assigned by the member _M_pi of shared_ptr, and we view the function _M_get_use_count(), when we call use_count() of weak_ptr, in fact we call the use_count() of shared_ptr, weak_ptr don't have the _Sp_counted_base itself, it`s the _Sp_counted_base of shared_ptr, so the weak_ptr must rely on the shared_ptr.

Let's get back to your question: "Is there a case that share_ptr ref count is 0, while weak_ptr ref count is not 0?"

I think using the normal way to write the code, the answer is NO !

  • Related