Объект, хранящий не владеющую ссылку, которая должна быть проинформирована перед уничтожением ссылки

У меня есть класс по этой схеме:

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_;
};

Foo все объекты принадлежат Bar:

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

private:
vector<Foo*> foos_;
};

Конечно, это упрощенный пример, чтобы получить идею. У меня есть гарантия, что нет петли Fooс, и что связано Fooвсе принадлежат к одному и тому же экземпляру Bar, Все идет нормально. Чтобы сделать вещи C ++ 11, я бы использовал vector< unique_ptr<Foo> > foos_; в Barи передать foos_[i].get() в качестве потенциального аргумента Foo конструктор.

Есть сделка:

Это приложение с графическим интерфейсом, и пользователь может в интерактивном режиме удалить некоторые Foo по желанию. Ожидаемое поведение таково, что если foo1 удаляется, и foo2 относительно foo1, затем foo2 становится теперь «абсолютным»:

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
}

Я знаю, что это можно сделать с помощью необработанных указателей, используя структуру DAG с обратными указателями, поэтому Foo осведомлены о том, кто «зависит от них», и могут проинформировать их перед удалением (возможные решения подробно описаны Вот а также Вот ).

Мой вопрос: вы бы справились с этим так же? Есть ли способ использовать стандартные интеллектуальные указатели C ++ 11, чтобы избежать явных обратных указателей, а затем избежать явного вызова areRelativeToMe_[i]->convertToAbsolute(); в деструкторе Foo? Я думал о weak_ptr, что-то в духе:

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

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

// ...
}

Но проблема в том, что convertToAbsolute() нуждается в родственнике Foo чтобы все еще существовать. Так что мне нужен не обладающий смарт-указатель, который может сказать «эта ссылка логически expired «, но фактически продлевает время жизни ссылочного объекта до тех пор, пока оно не понадобится.

Это можно увидеть либо как weak_ptr продление срока службы до тех пор, пока оно не будет передано другим 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
}

// ...
}

Или как shared_ptr с разным уровнем владения:

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
}
// ...
}

Какие-нибудь мысли?

6

Решение

Все Foo принадлежат Bar, Поэтому все удаления Foo случиться в Bar методы. Так что я мог бы реализовать эту логику внутри 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
}

Это гарантирует, что истекающий Foo все еще существует, когда convertToAbsolute называется на его иждивенцев.

Выбор того, как вычислить children зависит от тебя. Я бы наверное каждый Foo отслеживайте своих собственных детей (циклические не владеющие указателями), но вы также можете отслеживать это внутри Barили поиск через foos_ по требованию пересчитать его при необходимости.

1

Другие решения

Вы можете использовать подход двойной ссылки даже с более чем одним другим зависимым объектом. Вам нужно только связать вместе зависимости одного и того же объекта:

class Foo {
public:
explicit Foo(double x)
: v(x), foot(nullptr), next(nullptr), dept(nullptr) {}

// construct as relative object;  complexity O(1)
Foo(Foo*f, double x)
: v(x), foot(f), dept(nullptr)
{ foot->add_dept(this); }

// destruct;  complexity  O(n_dept) + O(foot->n_dept)
//                        O(1)  if !destroy_carefully
~Foo()
{
if(destroy_carefully) {
for(Foo*p=dept; p;) {
Foo*n=p->next;
p->unroot();
p=n;
}
if(foot) foot->remove_dept(this);
}
}

double x() const
{ return foot? foot->x() + v : v; }

private:

double v;   // my position relative to foot if non-null
Foo*foot;   // my foot point
Foo*next;   // next object with same foot point as me
Foo*dept;   // first object with me as foot point

// change to un-rooted;  complexity: O(1)
void unroot()
{ v+=foot->x(); foot=nullptr; next=nullptr; }

// add d to the linked list of dependents;  complexity O(1)
void add_dept(const Foo*d)
{ d->next=dept; dept=d; }

// remove d from the linked list of dependents ; complexity O(n_dept)
void remove_dept(const Foo*d)
{
for(Foo*p=dept; p; p=p->next)
if(p==d) { p=d->next; break; }
}
static bool destroy_carefully;

};
bool Foo::destroy_carefully = true;

Здесь настройка Foo::destroy_carefully=false позволяет удалить все оставшиеся объекты, не проходя распутывание взаимных ссылок (что может быть дорогостоящим).

1

Интересная проблема. Полагаю, вы поняли, что можете добавить указатель на объект ‘child’. Я не уверен, помогают ли умные указатели здесь. Я попытался реализовать код ниже, используя std::weak_ptr<Foo> но вы можете использовать его только для other_ и не для слушателя.

Еще одна мысль, которая у меня была, — это передать ответственность некой высшей силе. Проблема в том, что вы хотите выполнить обновление при вызове деструктора. Возможно, лучшим подходом будет позвонить convertToAbsolute() откуда-то еще Например, если вы храните Foos в векторе, и пользователь нажимает кнопку «удалить» в пользовательском интерфейсе, для удаления требуется индекс объекта, поэтому можно также обновить соседний элемент до абсолютного значения.

Ниже приведено решение, которое использует Foo*,

#include <iostream>
#include <memory>
#include <vector>class Foo
{
public:
// Create a Foo whose value is absolute
Foo(int x) : other_(nullptr), listener_(nullptr), a_(x)
{}

// Create a Foo whose value is relative to another Foo
Foo(Foo* other, int dx) :
other_(other), listener_(nullptr), a_(dx)
{
other->setListener(this);
}

~Foo()
{
convertToAbsolute();
if (listener_)
listener_->other_ = nullptr;
}

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

void setX(int i)
{
a_ = i;
}

void convertToAbsolute()
{
if (listener_)
listener_->a_ += a_;
}

void setListener(Foo* listener)
{
listener_ = listener;
}

private:
Foo* other_;
Foo* listener_;
int a_;
};void printFoos(const std::vector<std::shared_ptr<Foo>>& foos)
{
std::cout << "Printing foos:\n";
for(const auto& f : foos)
std::cout << '\t' << f->x() << '\n';
}

int main(int argc, const char** argv)
{
std::vector<std::shared_ptr<Foo>> foos;
try
{
auto foo1 = std::make_shared<Foo>(42);
auto foo2 = std::make_shared<Foo>(foo1.get(), 42);

foos.emplace_back(foo1);
foos.emplace_back(foo2);
}
catch (std::exception& e)
{
std::cerr << e.what() << '\n';
}

// Here, foo1->x() = 42 and foo2->x() = 84
printFoos(foos);

foos[0]->setX(10);
// Here, foo1->x() = 10 and foo2->x() = 52
printFoos(foos);

foos.erase(foos.begin());
// Here, foo2->x() = 52
printFoos(foos);

return 0;
}
1

Если у вас есть платформа Signal / Slot, это хорошее место для отвязки. Например, используя библиотеки Qt, эти классы могут выглядеть так:

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

// Create a Foo whose value is relative to another Foo
Foo(Foo * other, int dx) : QObject(nullptr) other_(other), a_(dx) {
connect(other, SIGNAL(foo_dying()), this, SLOT(make_absolute()));
}

~Foo() { emit foo_dying(); }

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

signals:
void foo_dying();

private slots:
void make_absolute()
{
a_ += other_->x();
other_ = nullptr;
}

private:
Foo * other_;
int a_;
};
1

Вот, пожалуй, самый простой способ достичь цели с помощью обратных указателей. Вы можете использовать нужный контейнер в зависимости от ваших требований к сложности (например, набор, хэш-таблица, вектор, связанный список и т. Д.). Более сложный, но более эффективный подход предложенный Уолтером.

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)
{
other->areRelativeToMe_.insert(this);
}

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

// delete the Foo
Foo::~Foo()
{
// Inform the one I depend on, if any, that I'm getting destroyed
if(other_)
other_->areRelativeToMe_.remove(this);

// Inform people that depends on me that I'm getting destructed
for(int i=0; i<areRelativeToMe_.size(); i++)
areRelativeToMe_[i]->convertToAbsolute();
}

private:
Foo * other_;
int a_;
Container<Foo*> areRelativeToMe_; // must provide insert(Foo*)
//          and remove(Foo*)

// Convert to absolute
void convertToAbsolute()
{
a_ += other_->x();
other_ = 0;
}
};
0
По вопросам рекламы [email protected]