Управление временем жизни функций-членов, связанных `std::bind`

В настоящее время я экспериментирую с написанием очереди событий на С++ 11. Я использую std::bind для получения объектов std::function, которые вызываются, когда происходят определенные события. Код для этого примерно выглядит так:

class A
{
public:
  void handle();
};

class B { ... };

// Later on, somewhere else...
std::vector< std::function< void() > functions;

A a;
B b;

functions.push_back( std::bind( &A::handle, &a ) );
functions.push_back( std::bind( &B::handle, &b ) );

// Even later:
for( auto&& f : functions )
  f(); // <--- How do I know whether f is still "valid"?

Есть ли способ гарантировать действительность объекта функции, чтобы я мог не споткнуться здесь о неопределенном поведении?

Я уже рассматривал этот вопрос здесь, std ::function в функцию-член объекта и время жизни объекта, но обсуждалось только, вызывает ли удаление указателя на связанный объект неопределенное поведение. Меня больше интересует, как справиться с уничтожением такого объекта. Есть ли способ обнаружить это?

РЕДАКТИРОВАТЬ: Чтобы уточнить, я знаю, что не могу гарантировать время жизни для нестатических, неглобальных объектов. Было бы достаточно получить уведомление об их уничтожении, чтобы можно было удалить недопустимые функциональные объекты.


person Gnosophilon    schedule 21.04.2015    source источник
comment
У функции-члена нет времени жизни, объект, который вы используете при привязке функции-члена, с другой стороны, имеет время жизни, и именно о времени жизни объектов вам нужно думать. И, к сожалению, невозможно гарантировать время жизни нестатических и неглобальных объектов.   -  person Some programmer dude    schedule 21.04.2015
comment
Да, ты прав. Можно ли получать уведомления об уничтожении нестатических, неглобальных объектов? Или я должен полагаться на соответствующие экземпляры класса для обработки удаления объектов функций при удалении?   -  person Gnosophilon    schedule 21.04.2015


Ответы (2)


Как заявил @Joachim, с функцией-членом не связано время жизни (это раздел кода, а не данные). Итак, вы спрашиваете, есть ли способ узнать, существует ли объект до выполнения обратного вызова.

Вы должны создать своего рода структуру, в которой объектный dctor уведомляет контейнер, когда он уничтожается, чтобы контейнер мог удалить его из своих «наблюдателей», вектор, содержащий все объекты. Для этого объект должен запомнить в своем экземпляре указатель на контейнер.

ОБНОВИТЬ

@Jason рассказывает об использовании shared_ptr. Их можно использовать, но в данном случае речь не идет о том, КАК уничтожить объект, связанный с другим списком уведомлений об объектах. Shared_ptr отложил уничтожение экземпляра до тех пор, пока не будут удалены все «управляемые» ссылки на него. Но если вам нужно уничтожить объект A И удалить все ссылки на него, потому что этот объект ДОЛЖЕН быть удален, вы должны просмотреть все контейнеры, в которых хранится shared_ptr, и удалить его. Очень болезненное занятие. Самое простое решение (использование raw ptr или shared_ptr, если вы можете их использовать, не имеет значения) — это двухканальное соединение между наблюдателем и наблюдаемым, таким образом, каждый может уведомить другого о своем уничтожении. Как хранить эту информацию? много способов сделать это: хеш-таблицы, слоты в наблюдателе и т. д.

person Mouze    schedule 21.04.2015
comment
Спасибо за ответ. Теперь я собираюсь управлять соединением с помощью вспомогательного объекта. Я просто подумал, что стоит обсудить возможные альтернативы, потому что мое решение не показалось мне очень элегантным. - person Gnosophilon; 23.04.2015

Один хак/обходной путь, который достигает желаемого результата, заключается в использовании параметра типа std::shared_ptr. Когда привязка уничтожается, то же самое происходит и с общим указателем, что будет правильно, когда это будет последняя ссылка. Однако это связано с изменением используемой подписи. Чтобы сделать это немного менее неудобным, вы можете использовать статические методы, которые принимают std::shared_ptr this - что-то вроде концепции параметра self в python, если вы знакомы.

Или, если у вас все в порядке с С++ 11, вы можете просто использовать лямбда-захват общего указателя.

Вам нужно будет динамически выделять экземпляры, чтобы использовать этот метод.

person Jason Newton    schedule 21.04.2015
comment
Это не заданный вопрос: если вы ставите shared_ptr, вы не решаете проблему уничтожения объекта; объект не уничтожается, а наблюдаемый по-прежнему выдает уведомления. Вы должны вручную перебирать и удалять все ссылки на удаляемый объект, это очень непрактично. - person Mouze; 21.04.2015
comment
Не могли бы вы пояснить по поводу лямбда-захвата? Я не уверен, что вы имеете в виду под этим. - person Gnosophilon; 21.04.2015
comment
@Mouze - вам нужно пройти весь путь до объекта shared_ptr для этих типов объектов. Объект не может быть уничтожен, пока существует допустимый экземпляр shared_ptr. Когда в этом примере список функций удаляет std::function с результатом std::bind, а других shared_ptr не существует, только тогда этот объект будет удален. - person Jason Newton; 22.04.2015
comment
@Gnosophilon: вы все равно должны использовать shared_ptrs в лямбда-подходе, но это делает так, что вам не нужно использовать методы, принимающие this в качестве параметра: [shared_this](){ shared_this->handle(); } Также обратите внимание на std::enable_shared_from_this - person Jason Newton; 22.04.2015
comment
@Jason shared_ptr не является панацеей на все случаи жизни. В этом случае бесполезен заданный вопрос: необходимо рассмотреть случай, когда приложение решает уничтожить объект, если этот объект присутствует в другом shared_ptr; например, если у вас есть один и тот же объект в 2 наблюдаемых списках, и вы выполняете удаление shared_ptr_to_object, вы действительно ничего не сделали, а только уменьшили счетчик. Объект все еще жив, все еще получает уведомление, и это не то поведение, которое требуется. Как я уже сказал, вам нужна функция unregister() в наблюдателе, которая хранит 1..N ptr/shared_ptr - person Mouze; 22.04.2015
comment
@Mouze Вопрос оператора немного изменился во времени. Первоначальный (и все еще актуальный) вопрос, как я его прочитал, заключается в том, чтобы гарантировать, что обратный вызов всегда может быть выполнен с допустимым объектом, а shared_ptr решает эту проблему, поскольку контейнер объектов функций теперь поддерживает эти объекты. Управление контейнером зарегистрированных обратных вызовов — это другая проблема, решаемая различными способами, например, с помощью итераторов с билетами из связанного списка, возвращаемого при регистрации. Boost.Asio регулярно демонстрирует паттерн shared_this с очень похожей проблемой, ознакомьтесь с их асинхронными туториалами. - person Jason Newton; 22.04.2015
comment
@Jason Спасибо, Джейсон, что сообщил мне о Boost.Asio. Я посмотрю на это. Я согласен, что мы переходим к более сложному решению, но, согласитесь, достаточно быть уведомленным об их уничтожении, значит именно это :) - person Mouze; 22.04.2015