Boost Asio — обработка резольвера и сокетов с помощью shared_ptr

У меня есть класс, который должен иметь возможность отправлять сообщения по TCP. Вот упрощенный интерфейс:

class CommandScreenshot : public CameraCommand
{
public:
    CommandScreenshot();
    ~CommandScreenshot();
    void Dispatch(boost::shared_ptr<boost::asio::io_service> io_service);

 private:
     void resolve_handler(const boost::system::error_code& err,
          boost::asio::ip::tcp::resolver::iterator endpoint_iterator);

};

Как видите, у меня есть функция Dispatch, которая на самом деле предназначена только для запуска асинхронной операции:

void CommandScreenshot::Dispatch(boost::shared_ptr<boost::asio::io_service> io_service)
{
    boost::asio::ip::tcp::resolver resolver(*io_service);
    boost::asio::ip::tcp::resolver::query query(m_hostname,"http");
    resolver.async_resolve(query,boost::bind(&CommandScreenshot::resolve_handler,this,boost::asio::placeholders::error, boost::asio::placeholders::iterator));
    return;
}

Все остальное будет сделано в следующих функциях обратного вызова. Объект io_service, а также соответствующий поток управляются другим классом (который имеет экземпляр CommandScreenshot и вызывает функцию Dispatch).

Теперь, чтобы реализовать простое TCP-соединение с Boost, вам нужны объекты resolver и socket, привязанные к объекту io_service. Поскольку объект io_service будет передан только во время вызова функции, я не могу инициализировать их в конструкторе класса. Также невозможно объявить их членами класса, а затем просто инициализировать в самой функции.

Моей первой идеей было просто инициализировать их при вызове функции и передать обработчику завершения. Это означало бы, что я объявляю оба объекта каждый раз, когда вызывается функция, и привязываю их к io_service. Затем в async_resolve я добавляю оба параметра через boost::bind. Это означало бы, что мой resolve_handler будет ожидать больше аргументов, например:

void resolve_handler(const boost::system::error_code& err,
          boost::asio::ip::tcp::resolver::iterator endpoint_iterator,
          boost::asio::ip::tcp::resolver resolver,
          boost::asio::ip::tcp::socket socket);

Я действительно сомневаюсь, что это достойное и справедливое решение. Обычно эти объекты следует хранить как элементы, а не копировать. Так что я еще раз подумал, и мой разум привел меня к boost::shared_ptr.

В моем заголовке это выглядит сейчас так:

// Stuff above stays the same
private:
  boost::shared_ptr<boost::asio::ip::tcp::resolver> m_resolver;
  boost::shared_ptr<boost::asio::ip::tcp::socket> m_socket;
// Stuff below stays the same

И реализация будет:

void CommandScreenshot::Dispatch(boost::shared_ptr<boost::asio::io_service> io_service)
{
    m_resolver.reset(new boost::asio::ip::tcp::resolver(*io_service));
    m_socket.reset(new boost::asio::ip::tcp::socket(*io_service));
    boost::asio::ip::tcp::resolver::query query(m_hostname,"http");
    m_resolver->async_resolve(query,boost::bind(&CommandScreenshot::resolve_handler,this,boost::asio::placeholders::error, boost::asio::placeholders::iterator));
    return;
}

При этом мне не нужно копировать около 4 (или, может быть, даже больше) параметров и связывать их вместе. Когда мне нужен объект сокета, я могу просто получить к нему доступ через указатель, который является членом класса.

Теперь мой простой вопрос -> это правильный путь? Функцию можно вызывать несколько раз, даже если асинхронная часть еще не завершена. (Я знаю, что тогда я должен защитить сокет и преобразователь с помощью мьютекса). Но чисто ли это, что я каждый раз создаю новый объект, когда вызываю функцию Dispatch? Достаточно ли вызова reset, чтобы избавиться от ненужной памяти?

Я знаю, что это длинный текст для конкретного короткого вопроса, и, кроме того, в нем даже нет ошибки. Но мне всегда хотелось бы знать, достойный ли это путь, которым я иду, и есть ли лучший путь, как бы я поступил.


person Toby    schedule 07.02.2012    source источник


Ответы (1)


Идея определить мемберов как shared_ptr для объектов asio вполне приемлема. Но вы не должны уничтожать и создавать их каждый раз снова:

if (!m_resolver)
{
  m_resolver.reset(...);
}

Кроме того, вы можете избежать явных блокировок, если убедитесь, что все операции над объектами asio выполняются в потоке, который запускает io_service (при условии, что у вас есть один поток для каждого io_service). Для этого просто отделите реализацию ваших интерфейсных функций и используйте post(). И, конечно же, используйте идиому shared_from_this, чтобы упростить управление временем жизни объекта:

void CommandScreenshot::someMethod(Arg1 arg1, Arg2 arg2)            
{
  io_.post(bind(&CommandScreenshot::someMethodImpl, shared_from_this, arg1, arg2)); 
}
//...
void CommandScreenshot::someMethodImpl(Arg1 arg1, Arg2 arg2)            
{
  // do anything you want with m_resolver, m_socket etc.
}
person Igor R.    schedule 22.02.2012