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

 David Schwartz06 июл. 2012 г., 09:09
@PSyton: я сделал то же самое, написавstatic вспомогательные функции, которые явно принимаютshared_ptr и вызвать функцию-член. Но я вижу так много кода, который делает это таким образом, я не могу поверить, что все это сломано. Тем не менее, я также не могу видеть, где это задокументировано для работы.
 PSyton06 июл. 2012 г., 08:39
Когда мы писали сетевой уровень для нашего проекта, мы добавляли внешние компоненты, чтобы гарантировать, что обработчик не будет разрушен при установлении соединения.

Ответы на вопрос(5)

Решение Вопроса

Короче,boost::bind создает копиюboost::shared_ptr<Connection> что возвращается изshared_from_this(), а такжеboost::asio может создать копию обработчика. Копия обработчика будет оставаться активной, пока не произойдет одно из следующих событий:

The handler has been called by a thread from which the service's run(), run_one(), poll() or poll_one() member function has been invoked. The io_service is destroyed. The io_service::service that owns the handler is shutdown via shutdown_service().

Вот соответствующие выдержки из документации:

boost::bind documentation:

The arguments that bind takes are copied and held internally by the returned function object.

boost::asio io_service::post:

The io_service guarantees that the handler will only be called in a thread in which the run(), run_one(), poll() or poll_one() member functions is currently being invoked. [...] The io_service will make a copy of the handler object as required.

boost::asio io_service::~io_service:

Uninvoked handler objects that were scheduled for deferred invocation on the io_service, or any associated strand, are destroyed.

Where an object's lifetime is tied to the lifetime of a connection (or some other sequence of asynchronous operations), a shared_ptr to the object would be bound into the handlers for all asynchronous operations associated with it. [...] When a single connection ends, all associated asynchronous operations complete. The corresponding handler objects are destroyed, and all shared_ptr references to the objects are destroyed.

В то время как от (2007),Предложение сетевой библиотеки для TR2 (Редакция 1) был получен из Boost.Asio. Раздел5.3.2.7. Requirements on asynchronous operations предоставляет некоторые детали для аргументовasync_ функции:

In this clause, an asynchronous operation is initiated by a function that is named with the prefix async_. These functions shall be known as initiating functions. [...] The library implementation may make copies of the handler argument, and the original handler argument and all copies are interchangeable.

The lifetime of arguments to initiating functions shall be treated as follows:

If the parameter is declared as a const reference or by-value [...] the implementation may make copies of the argument, and all copies shall be destroyed no later than immediately after invocation of the handler.

[...] Any calls made by the library implementation to functions associated with the initiating function's arguments will be performed such that calls occur in a sequence call1 to calln, where for all i, 1 ≤ i < n, calli precedes call i+1.

Таким образом:

The implementation may create a copy of the handler. In the example, the copied handler will create a copy of the shared_ptr<Connection>, increasing the reference count of the Connection instance while the copies of handler remain alive. The implementation may destroy the handler prior to invoking handler. This occurs if the async operation is outstanding when io_serive::service is shutdown or the io_service is destroyed. In the example, the copies of handler will be destroyed, decreasing the reference count of Connection, and potentially causing the Connection instance to be destroyed. If handler is invoked, then all copies of handler will immediately be destroyed once execution returns from the handler. Again, the copies of handler will be destroyed, decreasing the reference count of Connection, and potentially causing it to be destroyed. The functions associated with the asnyc_'s arguments, will be executed sequentially, and not concurrent. This includes io_handler_deallocate and io_handler_invoke. This guarantees that the handler will not be deallocated while the handler is being invoked. In most areas of the boost::asio implementation, the handler is copied or moved to stack variables, allowing the destruction to occur once execution exits the block in which it was declared. In the example, this ensures that the reference count for Connection will be at least one during the invocation of the handler.
 12 июн. 2017 г., 16:57
У кого-нибудь есть мысли по поводу этого паттерна w.r.t. спектакль? При каждой записи или чтении должен быть создан новый shared_ptr, который имеет дело с атомами / мьютексом и может значительно замедлить процесс. У кого-нибудь есть мысли о том, как продолжать передавать один и тот же shared_ptr вместо создания нового каждый раз?
 David Schwartz27 июл. 2017 г., 05:51
@schuess Вы хотите создать новыйshared_ptr каждый раз, потому что вы хотите сохранить объект живым и вы хотите удалить последний, чтобы удалить объект. На современных процессорах стоимость незначительна, потому что не будет почти никакой конкуренции, потому что операции по своей природе быстры и обычно выполняются на виртуальной нити. Такая «оптимизация» почти всегда будет введен в заблуждение.

Я также вижу, что этот шаблон часто используется, и (благодаря @Tanner) я могу понять, почему он используется, когдаio_service работает вmultiple threads, Тем не менее, я думаю, что у него все еще есть проблемы со временем жизни, поскольку он заменяет потенциальный сбой потенциальной утечкой памяти / ресурсов ...

Благодаря boost :: bind любые обратные вызовы, связанные с shared_ptrs, становятся & quot; пользователями & quot; объекта (увеличивая объекты use_count), поэтому объект не будет удален до тех пор, пока не будут вызваны все ожидающие обратные вызовы.

Обратные вызовы функций boost :: asio :: async * вызываются всякий раз, когда отменяется или закрывается вызывается на соответствующем таймере или сокете. Обычно вы просто делаете соответствующие вызовы отмены / закрытия в деструкторе, используя любимого Страуструпа.RAII шаблон; Работа выполнена.

Однако деструктор не будет вызван, когда владелец удалит объект, потому что обратные вызовы все еще содержат копии shared_ptrs, и поэтому их use_count будет больше нуля, что приведет к утечке ресурса. Утечки можно избежать, сделав соответствующие вызовы отмены / закрытия перед удалением объекта. Но это не так надежно, как использование RAII и выполнение вызовов отмены / закрытия в деструкторе. Обеспечение того, чтобы ресурсыalways освобожден даже при наличии исключений.

Шаблон, соответствующий 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_ не удаляется доafter другие задачи выполнены, т.е. когдаread_callback функция была вызвана.

 29 окт. 2013 г., 21:26
Спасибо @Tanner, я не учел время жизни буфера в многопоточном случае. Я исправляю, отредактировал свой ответ, чтобы пояснить, что шаблон weak_ptr должен использоваться только в однопоточной среде. Я заинтересован в вашемopaque pointer Решение, у вас есть пример?
 30 окт. 2013 г., 21:17
Хотя не совсем непрозрачный указатель,here является быстрым примером, который предоставляет семантику RAII пользователю, в то же время отделяя ее от времени жизни объекта.
 04 нояб. 2013 г., 23:27
Спасибо @Tanner, пример был интересным. Однако я чувствовал, что самый простой способ обеспечить поддержку многопоточности в моем ответе - просто поместить буфер вshared_ptr и передать его вbind к обратному вызову, чтобы он мог пережить класс Connection.
 29 окт. 2013 г., 05:33
Когда несколько потоков запускаютio_serviceНеопределенное поведение может произойти, поскольку время жизни базовой буферной памяти и сокета больше не гарантируется равным как минимум времени обработки. Возможно, стоит рассмотреть возможность использованияopaque pointer чтобы отделить желаемую семантику RAII с базовой продолжительностью жизни ресурсов. Это позволило бы закрыть сокет, когда пользовательский код больше не имеет дескриптораConnection, тем не менее, все еще позволяют необходимым ресурсам оставаться действительными в течение всего времени асинхронных операций.

Там нет преобразования из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, гарантируя, что нет никаких проблем жизни.

 06 июл. 2012 г., 10:20
@DavidSchwartz Помните, что существует оболочка вызова (давайте назовем ееw), то есть результат вызоваbind, который содержит копию умного указателя. При выполнении эквивалентаw(x, y, z) эта копия не уходит -w останется в живых, по крайней мере, до конца разговора.
 David Schwartz06 июл. 2012 г., 09:55
Вопрос в том, когда*f оценивается и какова продолжительность жизни*f является. Если это гарантировано, что необходимо для этого использования, где это задокументировано?

Может быть, я упускаю что-то очевидное здесь, но shared_ptr возвращаетсяshared_from_this() хранится в объекте функции, возвращаемойboost::bind, который поддерживает это. Он только неявно преобразуется вConnection* в тот момент, когда обратный вызов запускается, когда завершается асинхронное чтение, и объект сохраняется в течение, по крайней мере, продолжительности вызова. Еслиhandle_Receive из этого не создается еще один shared_ptr, а shared_ptr, который был сохранен в функторе связывания, является последним живым shared_ptr, объект будет уничтожен после возврата обратного вызова.

 David Schwartz11 июл. 2012 г., 20:32
Мне неудобно полагаться на рассуждения о том, как должна быть реализована функция, на том основании, что мы не можем придумать какой-либо другой способ сделать это. Мне действительно удобно полагаться на гарантированную семантику операции.
 David Schwartz06 июл. 2012 г., 09:54
Что обеспечивает поддержание объекта в течение хотя бы продолжительности вызова? Где это задокументировано?
 06 июл. 2012 г., 10:13
Потому что вы не можете позвонитьoperator() функтора связывания, если объект функции больше не существует. : P только послеoperator() функтора связывания возвращается, может ли функтор быть уничтожен логически.

Это выглядит так:

1) Boost.Bind документациясостояния:

"[Note: mem_fn creates function objects that are able to accept a pointer, a reference, or a smart pointer to an object as its first argument; for additional information, see the mem_fn documentation.]"

2) документация mem_fnговорит:

When the function object is invoked with a first argument x that is neither a pointer nor a reference to the appropriate class (X in the example above), it uses get_pointer(x) to obtain a pointer from x. Library authors can "register" their smart pointer classes by supplying an appropriate get_pointer overload, allowing mem_fn to recognize and support them.

Таким образом, указатель или смарт-указатель сохраняется в связывателе как есть до его вызова.

Ваш ответ на вопрос