Search code examples
c++shared-ptrsmart-pointersweak-ptr

Locating a weak_ptr after shared_ptr is expired


I have a struct, A, objects of which are managed by shared_ptrs. Struct A holds a reference to struct B. B objects need to keep track of which A objects hold references to them, and also need to be able to return shared_ptrs to these objects. To make this easy, I'm storing a set of weak_ptrs to associated A objects inside of B. So far, so good.

My problem is that I want A's destructor to remove the reference to itself from its associcated B object. However, (what I thought was) the obvious solution doesn't work, as by the time A's destructor is called, its associated weak_ptrs are expired, making it difficult to delete from the set. Here's what I tried:

#include <memory>
#include <set>

struct A;
struct B;

struct B{
   std::set<std::weak_ptr<A>, std::owner_less<std::weak_ptr<A>>> set_of_a;
};

struct A : std::enable_shared_from_this<A>{
   B &b;
   A(B &b):b(b){};
   ~A(){
      // bad_weak_ptr exception here
      b.set_of_a.erase(shared_from_this());
   }
};


int main(){
   B b;
   std::shared_ptr<A> a1 = std::make_shared<A>(b);
   b.set_of_a.insert(a1);
   {
      std::shared_ptr<A> a2 = std::make_shared<A>(b);
      b.set_of_a.insert(a2);
   }
   return 0;
}

What is the correct way to accomplish this? I could just have A's destructor run through B's set and erase any expired weak_ptrs, but that doesn't seem clean. I could also just convert B's set to raw A pointers, and use those raw pointers to access A's shared_from_this() when needed, but I can't help but think I'm just doing something wrong.


Solution

  • Since you haven't mentioned the compiler - if you are on a new enough compiler, then you could use weak_from_this (available from C++17):

    b.set_of_a.erase(weak_from_this());
    

    This would actually achieve what you want in a clean way since then you would just be comparing the actual weak_ptr instances instead of trying to create a new shared instance in the dtor, which logically fails now. Seems to work on coliru, but did not work on VS2017 (15.4.1) with C++17 enabled.

    Update for curiousguy

    This snippet:

    #include <memory>
    #include <set>
    #include <iostream>
    
    struct A;
    struct B;
    
    struct B{
       std::set<std::weak_ptr<A>, std::owner_less<std::weak_ptr<A>>> set_of_a;
    };
    
    struct A : std::enable_shared_from_this<A>{
       B &b;
       A(B &b):b(b){};
       ~A(){
          b.set_of_a.erase(weak_from_this());
          std::cout << "Size of set_of_a: " << b.set_of_a.size() << "\n";
       }
    };
    
    
    int main(){
       B b;
       std::shared_ptr<A> a1 = std::make_shared<A>(b);
       b.set_of_a.insert(a1);
       {
          std::shared_ptr<A> a2 = std::make_shared<A>(b);
          b.set_of_a.insert(a2);
       }
       return 0;
    }
    

    gives output:

    Size of set_of_a: 1
    Size of set_of_a: 0
    

    on coliru (gcc 8).