Boost async_ * функции и shared_ptr's
Я часто вижу этот шаблон в коде, привязывая 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
на сильном указателе, а затем вызов функции-члена по возвращенному указателю недостаточен, если сильный указатель не будет уничтожен до тех пор, пока функция-член не вернется.
Ответы
Ответ 1
Короче говоря, boost::bind
создает копию boost::shared_ptr<Connection>
, которая возвращается из shared_from_this()
, а boost::asio
может создать копию обработчика. Копия обработчика останется в живых до тех пор, пока не произойдет одно из следующих действий:
- Обработчик был вызван потоком, из которого были вызваны функции
run()
, run_one()
, poll()
или poll_one()
службы poll_one()
.
-
io_service
уничтожен.
-
io_service::service
, которому принадлежит обработчик, отключается через shutdown_service()
.
Вот соответствующие выдержки из документации:
-
boost:: bind документация:
Аргументы, которые принимает bind
, копируются и удерживаются внутри объекта возвращаемой функции.
-
boost:: asio io_service::post
:
io_service
гарантирует, что обработчик будет вызван только в потоке, в котором в настоящее время вызывается функции-члены run()
, run_one()
, poll()
или poll_one()
. [...] io_service
сделает копию объекта обработчика по мере необходимости.
-
boost:: 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 ≤ я < n, вызов i предшествует вызову я + 1.
Таким образом:
- Реализация может создать копию обработчика. В этом примере скопированный обработчик создаст копию
shared_ptr<Connection>
, увеличив счетчик ссылок экземпляра Connection
, пока копии обработчика останутся в живых.
- Реализация может уничтожить обработчик до вызова обработчика. Это происходит, если операция async выдающаяся, когда
io_serive::service
выключается или io_service
уничтожается. В этом примере копии обработчика будут уничтожены, уменьшится количество ссылок Connection
и потенциально приведет к уничтожению экземпляра Connection
.
- Если обработчик вызывается, то все копии обработчика будут немедленно уничтожены после выполнения возврата из обработчика. Опять же, копии обработчика будут уничтожены, уменьшится количество ссылок
Connection
и потенциально может быть уничтожено.
- Функции, связанные с аргументами
asnyc_
, будут выполняться последовательно, а не одновременно. Это включает io_handler_deallocate
и io_handler_invoke
. Это гарантирует, что обработчик не будет освобожден при вызове обработчика. В большинстве областей реализации boost::asio
обработчик копируется или перемещается в стек переменных, позволяя разрушение возникать после того, как выполнение завершает выполнение блока, в котором он был объявлен. В этом примере это гарантирует, что счетчик ссылок для Connection
будет по крайней мере один во время вызова обработчика.
Ответ 2
Это происходит следующим образом:
1) Документация Boost.Bind состояния:
"[Примечание: mem_fn создает объекты функций, которые могут принимать указатель, ссылку или умный указатель на объект в качестве первого аргумент; для получения дополнительной информации см. документацию mem_fn.]"
2) документация mem_fn говорит:
Когда объект функции вызывается с первым аргументом x, который является ни указатель, ни ссылка на соответствующий класс (X в пример выше), он использует get_pointer (x) для получения указателя от x. Авторы библиотек могут "регистрировать" свои классы интеллектуальных указателей обеспечивая соответствующую перегрузку get_pointer, позволяя mem_fn узнавать и поддерживать их.
Таким образом, указатель или смарт-указатель хранятся в связующем как есть, до его вызова.
Ответ 3
Я также вижу, что этот шаблон используется много и (благодаря @Tanner). Я вижу, почему он использовался, когда io_service
выполняется в нескольких потоках. Тем не менее, я думаю, что с ним все еще возникают проблемы, поскольку он заменяет потенциальный сбой потенциальной утечкой памяти/ресурса...
Благодаря boost:: bind, любые обратные вызовы, привязанные к shared_ptrs, становятся "пользователями" объекта (увеличивая количество объектов use_count), поэтому объект не будет удален до тех пор, пока не будут вызваны все выдающиеся обратные вызовы.
Обратные вызовы функций boost:: asio:: async * вызывается всякий раз, когда отменяется или закрывается
вызывается на соответствующий таймер или сокет. Обычно вы просто делаете соответствующие отмены/закрытия вызовов в деструкторе, используя шаблон Straustrup, любимый RAII; работа выполнена.
Однако деструктор не вызывается, когда владелец удаляет объект, потому что обратные вызовы все еще хранят копии shared_ptrs, поэтому их use_count будет больше нуля, что приведет к утечке ресурсов. Утечки можно избежать, сделав соответствующие отмены/закрытия вызовов до удаления объекта. Но это не так сложно, как использование RAII, и делает отмену/закрытие вызовов в деструкторе. Обеспечение того, чтобы ресурсы всегда были освобождены даже при наличии исключений.
Соответствующий шаблон RAII должен использовать статические функции для обратных вызовов и передавать weak_ptr для 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
была вызвана.
Ответ 4
Нет преобразования из boost::shared_ptr<Connection>
(возвращаемого типа shared_from_this
) в Connection*
(тип this
), так как это было бы небезопасно, как вы по праву указали.
Магия в Boost.Bind. Проще говоря, при вызове формы bind(f, a, b, c)
(в этом примере не задействовано замещающее или вложенное выражение привязки), где f
является указателем на член, тогда вызов результата вызова приведет к вызову form (a.*f)(b, c)
, если a
имеет тип, полученный из типа класса указателя на член (или тип boost::reference_wrapper<U>
), или же его формы ((*a).*f)(b, c)
. Это работает с указателями и умными указателями. (Я на самом деле работаю из памяти правил для std::bind
, Boost.Bind не совсем идентичен, но оба находятся в одном и том же духе.)
Кроме того, результат shared_from_this()
сохраняется в результате вызова bind
, гарантируя отсутствие проблем с продолжительностью жизни.
Ответ 5
Возможно, мне не хватает чего-то очевидного здесь, но shared_ptr, возвращаемый shared_from_this()
, хранится в объекте функции, возвращаемом boost::bind
, который сохраняет его в памяти. Он только неявно преобразуется в Connection*
в момент, когда обратный вызов запускается, когда чтение async завершается, и объект сохраняется в живых, по крайней мере, в течение продолжительности вызова. Если handle_Receive
не создает другого shared_ptr из этого, а shared_ptr, который был сохранен в функторе связывания, является последним shared_ptr вживую, объект будет уничтожен после возврата обратного вызова.