Усилить функции async_* и shared_ptr
Я часто вижу этот шаблон в коде shared_from_this
в качестве первого параметра для функции-члена и отправки результата с помощью async_*
функция. Вот пример из другого вопроса:
void Connection::Receive()
{
boost::asio::async_read(socket_,boost::asio::buffer(this->read_buffer_),
boost::bind(&Connection::handle_Receive,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
Единственная причина использовать shared_from_this()
вместо this
поддерживать объект до тех пор, пока не будет вызвана функция-член. Но если где-то нет какой-то магии усиления, так как this
указатель имеет тип Connection*
, это все handle_Receive
может взять, и возвращенный умный указатель должен быть немедленно преобразован в обычный указатель. Если это произойдет, нет ничего, чтобы сохранить объект в живых. И, конечно же, в вызове нет указателя shared_from_this
,
Тем не менее, я видел эту модель так часто, что не могу поверить, что она полностью сломана, как мне кажется. Есть ли какая-то магия Boost, которая приводит к тому, что shared_ptr будет преобразован в обычный указатель позже, когда операция завершится? Если так, это где-то задокументировано?
В частности, где-то задокументировано, что общий указатель будет существовать до завершения операции? призвание get_pointer
на сильном указателе и последующего вызова функции-члена на возвращенном указателе недостаточно, если только сильный указатель не будет уничтожен, пока функция-член не вернется.
5 ответов
Короче, boost::bind
создает копию boost::shared_ptr<Connection>
что возвращается из shared_from_this()
, а также boost::asio
может создать копию обработчика. Копия обработчика будет оставаться активной, пока не произойдет одно из следующих событий:
- Обработчик был вызван потоком, из которого сервис
run()
,run_one()
,poll()
или жеpoll_one()
функция-член была вызвана. -
io_service
уничтожен -
io_service::service
который владеет обработчиком выключен черезshutdown_service()
,
Вот соответствующие выдержки из документации:
boost:: bind документация:
Аргументы, которые
bind
дубли копируются и хранятся внутри возвращенного функционального объекта.повышение:: ASIO
io_service::post
:io_service
гарантирует, что обработчик будет вызываться только в потоке, в которомrun()
,run_one()
,poll()
или жеpoll_one()
функции-члены в настоящее время вызывается. [...]io_service
сделает копию объекта- обработчика, как требуется.повышение:: ASIO
io_service::~io_service
:Незавершенные объекты-обработчики, запланированные для отложенного вызова на
io_service
или любая связанная нить уничтожены.
Если время жизни объекта привязано к времени жизни соединения (или некоторой другой последовательности асинхронных операций),shared_ptr
чтобы объект был связан с обработчиками для всех асинхронных операций, связанных с ним. [...] Когда заканчивается одно соединение, все связанные асинхронные операции завершаются. Соответствующие объекты-обработчики уничтожены, и всеshared_ptr
ссылки на объекты уничтожены.
В то время как от (2007), Предложение Сетевой библиотеки для TR2 (Версия 1) было получено из Boost.Asio. Раздел 5.3.2.7. Requirements on asynchronous operations
предоставляет некоторые детали для аргументов async_
функции:
В этом пункте асинхронная операция инициируется функцией с именем с префиксом
async_
, Эти функции должны быть известны как инициирующие функции. [...] Реализация библиотеки может создавать копии аргумента обработчика, а исходный аргумент обработчика и все копии являются взаимозаменяемыми.Время жизни аргументов инициирующих функций должно рассматриваться следующим образом:
- Если параметр объявлен как константная ссылка или по значению [...], реализация может сделать копии аргумента, и все копии должны быть уничтожены не позднее, чем сразу после вызова обработчика.
[...] Любые вызовы, сделанные реализацией библиотеки для функций, связанных с аргументами инициирующей функции, будут выполняться так, чтобы вызовы происходили в последовательном вызове 1 для вызова n, где для всех i, 1 ≤ i < n, вызов i предшествует позвони мне + 1.
Таким образом:
- Реализация может создать копию обработчика. В этом примере скопированный обработчик создаст копию
shared_ptr<Connection>
, увеличивая счетчик ссылокConnection
экземпляр, пока копии обработчика остаются живыми. - Реализация может уничтожить обработчик до вызова обработчика. Это происходит, если асинхронная операция не выполняется, когда
io_serive::service
выключение илиio_service
уничтожен В этом примере копии обработчика будут уничтожены, уменьшив количество ссылок наConnection
и, возможно, вызываетConnection
экземпляр должен быть уничтожен. - Если вызывается обработчик, то все копии обработчика будут немедленно уничтожены, как только выполнение вернется из обработчика. Опять же, копии обработчика будут уничтожены, уменьшая количество ссылок
Connection
и потенциально вызывая его уничтожение. - Функции, связанные с
asnyc_
Аргументы будут выполняться последовательно, а не одновременно. Это включаетio_handler_deallocate
а такжеio_handler_invoke
, Это гарантирует, что обработчик не будет освобожден во время его вызова. В большинстве районовboost::asio
В этом случае обработчик копируется или перемещается в переменные стека, что позволяет уничтожить его после выхода из блока, в котором он был объявлен. В этом примере это гарантирует, что счетчик ссылок дляConnection
будет хотя бы один во время вызова обработчика.
Это выглядит так:
1) Документация Boost.Bind гласит:
"[Примечание: mem_fn создает функциональные объекты, которые могут принимать указатель, ссылку или интеллектуальный указатель на объект в качестве первого аргумента; дополнительную информацию см. В документации mem_fn.]"
2) документация mem_fn гласит:
Когда объект функции вызывается с первым аргументом x, который не является ни указателем, ни ссылкой на соответствующий класс (X в приведенном выше примере), он использует get_pointer(x) для получения указателя из x. Авторы библиотеки могут "зарегистрировать" свои классы интеллектуальных указателей, предоставив соответствующую перегрузку get_pointer, позволяющую mem_fn распознавать и поддерживать их.
Таким образом, указатель или смарт-указатель сохраняется в связывателе как есть, до его вызова.
Я также вижу, что этот шаблон часто используется и (благодаря @Tanner) я понимаю, почему он используется, когда io_service
запускается в нескольких потоках. Тем не менее, я думаю, что у него все еще есть проблемы со временем жизни, поскольку он заменяет потенциальный сбой потенциальной утечкой памяти / ресурсов...
Благодаря boost::bind любые обратные вызовы, связанные с shared_ptrs, становятся "пользователями" объекта (увеличивая объекты use_count), поэтому объект не будет удален, пока не будут вызваны все ожидающие обратные вызовы.
Обратные вызовы функций boost::asio::async* вызываются всякий раз, когда отменяется или закрывается соответствующий таймер или сокет. Обычно вы просто делаете соответствующие вызовы отмены / закрытия в деструкторе, используя любимый паттерн RAII Страуструпа; Работа выполнена.
Однако деструктор не будет вызван, когда владелец удалит объект, потому что обратные вызовы по-прежнему содержат копии shared_ptrs, и поэтому их use_count будет больше нуля, что приведет к утечке ресурса. Утечки можно избежать, сделав соответствующие вызовы отмены / закрытия перед удалением объекта. Но это не так надежно, как использование RAII и выполнение вызовов отмены / закрытия в деструкторе. Обеспечение того, чтобы ресурсы всегда были освобождены, даже при наличии исключений.
Шаблон, соответствующий RAII, состоит в использовании статических функций для обратных вызовов и передаче слабого_потора в boost::bind при регистрации функции обратного вызова, как в примере ниже:
class Connection : public boost::enable_shared_from_this<Connection>
{
boost::asio::ip::tcp::socket socket_;
boost::asio::strand strand_;
/// shared pointer to a buffer, so that the buffer may outlive the Connection
boost::shared_ptr<std::vector<char> > read_buffer_;
void read_handler(boost::system::error_code const& error,
size_t bytes_transferred)
{
// process the read event as usual
}
/// Static callback function.
/// It ensures that the object still exists and the event is valid
/// before calling the read handler.
static void read_callback(boost::weak_ptr<Connection> ptr,
boost::system::error_code const& error,
size_t bytes_transferred,
boost::shared_ptr<std::vector<char> > /* read_buffer */)
{
boost::shared_ptr<Connection> pointer(ptr.lock());
if (pointer && (boost::asio::error::operation_aborted != error))
pointer->read_handler(error, bytes_transferred);
}
/// Private constructor to ensure the class is created as a shared_ptr.
explicit Connection(boost::asio::io_service& io_service) :
socket_(io_service),
strand_(io_service),
read_buffer_(new std::vector<char>())
{}
public:
/// Factory method to create an instance of this class.
static boost::shared_ptr<Connection> create(boost::asio::io_service& io_service)
{ return boost::shared_ptr<Connection>(new Connection(io_service)); }
/// Destructor, closes the socket to cancel the read callback (by
/// calling it with error = boost::asio::error::operation_aborted) and
/// free the weak_ptr held by the call to bind in the Receive function.
~Connection()
{ socket_.close(); }
/// Convert the shared_ptr to a weak_ptr in the call to bind
void Receive()
{
boost::asio::async_read(socket_, boost::asio::buffer(read_buffer_),
strand_.wrap(boost::bind(&Connection::read_callback,
boost::weak_ptr<Connection>(shared_from_this()),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred,
read_buffer_)));
}
};
Обратите внимание read_buffer_
хранится как shared_ptr
в классе Connection и передается в read_callback
функционировать как shared_ptr
,
Это для того, чтобы где несколько io_services
выполняются в отдельных задачах, read_buffer_
не удаляется до тех пор, пока не будут выполнены другие задачи, т.е. read_callback
функция была вызвана.
Там нет преобразования из boost::shared_ptr<Connection>
(возвращаемый тип shared_from_this
) чтобы Connection*
(тип this
), так как это было бы небезопасно, как вы правильно отметили.
Магия в Boost.Bind. Проще говоря, в вызове формы bind(f, a, b, c)
(для этого примера не используется ни заполнитель, ни вложенное выражение связывания), где f
указатель на член, тогда вызов результата вызова приведет к вызову формы (a.*f)(b, c)
если a
имеет тип, производный от типа класса указателя на член (или тип boost::reference_wrapper<U>
) или же он имеет вид ((*a).*f)(b, c)
, Это работает как с указателями, так и с умными указателями. (Я на самом деле работаю из памяти правила std::bind
, Boost.Bind не совсем идентичен, но оба в том же духе.)
Кроме того, результат shared_from_this()
сохраняется в результате вызова bind
, гарантируя, что нет никаких проблем жизни.
Может быть, я упускаю что-то очевидное здесь, но shared_ptr возвращается shared_from_this()
хранится в объекте функции, возвращаемой boost::bind
, который поддерживает это. Это только неявно преобразуется в Connection*
в тот момент, когда обратный вызов запускается, когда завершается асинхронное чтение, и объект сохраняется в течение, по крайней мере, продолжительности вызова. Если handle_Receive
не создает другого shared_ptr из этого, и shared_ptr, который был сохранен в функторе связывания, является последним живым shared_ptr, объект будет уничтожен после возврата обратного вызова.