Search code examples
c++pointerscircular-dependencyobject-lifetime

Lifetime of dependent classses in C++?


I have a class A that provides methods to construct instances of class B. And B holds a private reference to A and provides a constructor to set this reference.

class A { 
   public: 
     B* construct_B ();
}
class B {
  private:
    const A& private_A;
  public:
    B ( const A& my_A ): private_A (my_A) { }
}

The implementation of construct_B takes care of dynamic allocation and passing the reference to itself via this.

How do I implement this setup in such a way that I make sure that the lifetime of A is longer than B so that its reference remains valid? Notice that I don't care about all the possibilities of construct_B instead of returning a raw pointer I could return a smart pointer or similar.

One possible way of solving this could be having B instead of holding a reference to hold a smart pointer to A, and instead of dynamically allocating B in construct_B to take a static reference to B and then set it's pointer, something like

class A : 
   public std::enable_shared_from_this<A> { 
   public: 
      void setup_B ( const B& my_B ) { 
        my_B.set_A (shared_ptr_from_this() ) ;
}
class B {
  private:
    const shared_ptr<A> private_A_ptr;
  public:
    void set_A ( const shared_ptr<A> my_A ): 
        private_A_ptr (my_A) { }
}

which then could be implemented by int main () { A static_A; B static_B; A.setup_B (static_B); }

Does the shared_ptr of this last construction avoid the problem of A being deleted before B?


Solution

  • shared_ptr is your answer. Something like this:

    #include <memory>
    
    struct A;
    
    class B {
        const std::shared_ptr<A> private_A_ptr;
      public:
      B(std::shared_ptr<A> parent) : private_A_ptr(std::move(parent)) {}
    };
    
    struct A : 
    std::enable_shared_from_this<A> 
    { 
      B make_b() {
        return B(shared_from_this());
      }
    };
    
    int main()
    {
      // this would be invalid:  
      //A a;
      //auto b = a.make_b();
    
      // but this is fine
    
      auto pa = std::make_shared<A>();
      auto b = pa->make_b();
    
      // later...
      pa.reset();
    
      // A still exists because ownership was shared with b
    
    }