Усилить функции 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, объект будет уничтожен после возврата обратного вызова.

Другие вопросы по тегам