Search code examples
c++c++11smart-pointersweak-ptr

Object storing a non-owning reference that must be informed before the reference is destructed


I have a class following this pattern:

class Foo
{
public:
    // Create a Foo whose value is absolute
    Foo(int x) : other_(0), a_(x)  {}

    // Create a Foo whose value is relative to another Foo
    Foo(Foo * other, int dx) : other_(other), a_(dx) {}

    // Get the value
    double x() const
    {
        if(other_)
            return other_->x() + a_;
        else
            return a_;
    }

private:
    Foo * other_;
    int a_;
};

The Foo objects are all owned by a Bar:

class Bar
{
public:
    ~Bar() { for(int i=0; i<foos_.size(); i++) delete foos_[i]; }

private:
    vector<Foo*> foos_;
};

Of course, this is a simplified example to get the idea. I have a guarantee that there are no loop of Foos, and that linked Foos all belong to the same instance of Bar. So far, so good. To do things the C++11 way, I would use vector< unique_ptr<Foo> > foos_; in Bar, and pass foos_[i].get() as potential argument of a Foo constructor.

There is the deal:

This a GUI application, and the user can interactively delete some Foo at will. The expected behaviour is that if foo1 is deleted, and foo2 is relative to foo1, then foo2 becomes now "absolute":

void Foo::convertToAbsolute() { a_ += other_->x(); other_ = 0; }

void usageScenario()
{
    Foo * foo1 = new Foo(42);      
    Foo * foo2 = new Foo(foo1, 42);
    // Here, foo1->x() = 42 and foo2->x() = 84

    foo1->setX(10);
    // Here, foo1->x() = 10 and foo2->x() = 52

    delete foo1;
    // Here, foo2->x() = 52
}

I know it is possible to do it using raw pointers, by using a a DAG structure with back-pointers, so the Foo are aware of who "depends on them", and can inform them before deletion (possible solutions detailed here and here ).

My question is: Would you handle it the same way? Is there a way using standard C++11 smart pointers to avoid having the explicit back-pointers, and then avoid explicitely calling areRelativeToMe_[i]->convertToAbsolute(); in the destructor of Foo? I was thinking about weak_ptr, something in the spirit of:

class Foo { /* ... */ weak_ptr<Foo> other_; };

double Foo::x() const
{
    if(other_.isExpired())
        convertToAbsolute();

    // ...
}

But the issue is that convertToAbsolute() needs the relative Foo to still exist. So I need a non-owning smart-pointer that can tell "this reference is logically expired", but actually extends the lifetime of the referenced object, until it is not needed.

It could be seen either like a weak_ptr extending the lifetime until it is not shared with any other weak_ptr:

class Foo { /* ... */ extended_weak_ptr<Foo> other_; };

double Foo::x() const
{
    if(other_.isExpired())
    {
        convertToAbsolute();
        other_.reset(); // now the object is destructed,  unless other
                          // foos still have to release it
    }

    // ...
}

Or like a shared_ptr with different level of ownership:

class Bar { /* ... */ vector< multilevel_shared_ptr<Foo> foos_; };

class Foo { /* ... */ multilevel_shared_ptr<Foo> other_; };

void Bar::createFoos()
{ 
    // Bar owns the Foo* with the highest level of ownership "Level1"

    // Creating an absolute Foo
    foos_.push_back( multilevel_unique_ptr<Foo>(new Foo(42), Level1) );

    // Creating a relative Foo 
    foos_.push_back( multilevel_unique_ptr<Foo>(new Foo(foos_[0],7), Level1) );
}

Foo::Foo(const multilevel_unique_ptr<Foo> & other, int dx) :
    other_( other, Level2 ),
   // Foo owns the Foo* with the lowest level of ownership "Level2"
    a_(dx) 
{
}

double Foo::x() const
{
    if(other_.noLevel1Owner()) // returns true if not shared 
                               // with any Level1 owner
    {
        convertToAbsolute();
        other_.reset(); // now the object is destructed, unless 
                        // shared with other Level2 owners
    }
    // ...
}

Any thoughts?


Solution

  • All Foo are owned by Bar. Therefore all deletions of Foo happen in Bar methods. So I might implement this logic inside Bar:

    void Bar::remove(Foo* f)
    {
        using namespace std::placeholders;
        assert(std::any_of(begin(foos_), end(foos_),
                           std::bind(std::equal_to<decltype(f)>(), f, _1));
    
        auto const& children = /* some code which determines which other Foo depend on f */;
        std::for_each(begin(children), end(children),
                      std::mem_fn(&Foo::convertToAbsolute));
        foos_.remove(f);
    
        delete f; // not needed if using smart ptrs
    }
    

    This would ensure that the expiring Foo still exists when convertToAbsolute is called on its dependents.

    The choice of how to compute children is up to you. I would probably have each Foo keep track of its own children (cyclic non-owning pointers), but you could also keep track of it inside Bar, or search through foos_ on demand to recompute it when needed.