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 Foo
s, and that linked Foo
s 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?
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.