提升async_ *函数和shared_ptr

时间:2012-07-06 06:10:09

标签: c++ boost boost-asio 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 个答案:

答案 0 :(得分:21)

简而言之,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 documentation

      

    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年),Networking Library Proposal for TR2 (Revision 1)来自Boost.Asio。第5.3.2.7. Requirements on asynchronous operations节提供了async_函数参数的一些细节:

  

在本节中,异步操作由以前缀async_命名的函数启动。这些功能应称为启动功能。 [...]库实现可以制作处理程序参数的副本,和   原始处理程序参数和所有副本都是可互换的。

     

启动函数的参数的生命周期应按如下方式处理:

     
      
  • 如果参数声明为const引用或by-value [...],则实现可以复制参数,并且所有副本应在不迟于调用处理程序之后立即销毁。
  •   
     

[...]将执行库实现对与启动函数的参数关联的函数的任何调用,以便调用在序列调用 1 中发生调用 n ,其中对于所有 i ,1≤ i &lt; n ,在 i + 1 之前调用 i

因此:

  • 实现可以创建处理程序的副本。在示例中,复制的处理程序将创建shared_ptr<Connection>的副本,增加Connection实例的引用计数,而处理程序的副本保持活力。
  • 在调用处理程序之前,实现可能会破坏处理程序。如果在io_serive::service关闭或io_service被销毁时异步操作未完成,则会发生这种情况。在该示例中,将销毁处理程序的副本,从而减少Connection的引用计数,并可能导致Connection实例被销毁。
  • 如果调用了 handler ,那么一旦执行从处理程序返回,处理程序的所有副本将立即被销毁。同样,处理程序的副本将被销毁,减少Connection的引用计数,并可能导致它被销毁。
  • asnyc_参数关联的函数将按顺序执行,而不是并发执行。其中包括io_handler_deallocateio_handler_invoke。这可以保证在调用处理程序时不会释放处理程序。在boost::asio实现的大多数区域中,处理程序被复制或移动到堆栈变量,允许在执行退出声明它的块时发生破坏。在示例中,这可确保在调用处理程序期间Connection的引用计数至少为一。

答案 1 :(得分:5)

它是这样的:

1)Boost.Bind文档states

  

“[注意:mem_fn创建能够接受的函数对象   指针,引用或指向对象的智能指针作为其第一个   参数;有关其他信息,请参阅mem_fn文档。]“

2)mem_fn文档says

  

当使用第一个参数x调用函数对象 时   既不是指针也不是对相应类的引用(X中的   例如,它使用get_pointer(x)从x获取指针。   库作者可以通过“注册”他们的智能指针类   提供适当的get_pointer重载,允许mem_fn   认识并支持他们。

因此,指针或智能指针按原样存储在绑定器中,直到调用它为止。

答案 2 :(得分:4)

我也看到这种模式使用了很多(感谢@Tanner)我可以看到为什么在{strong>多线程中运行io_service时使用它。但是,我认为它仍然存在生命周期问题,因为它可能会因潜在的内存/资源泄漏而取代潜在的崩溃......

感谢boost :: bind,绑定到shared_ptrs的任何回调都成为对象的“用户”(增加对象use_count),因此在调用所有未完成的回调之前,不会删除该对象。 / p>

每当,取消或关闭时,都会调用boost :: asio :: async *函数的回调 在相关的计时器或套接字上调用。通常,您只需使用Stroustrup的心爱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_在Connection类中存储为shared_ptr,并作为read_callback传递给shared_ptr函数。

这是为了确保在单独的任务中运行多个io_services时,{<1}}不会被删除,直到 其他任务完成后,即read_buffer_read_callback 1}}函数已被调用。

答案 3 :(得分:2)

没有从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调用的结果中,确保没有生命周期问题。

答案 4 :(得分:1)

也许我在这里遗漏了一些明显的东西,但shared_from_this()返回的shared_ptr存储在boost::bind返回的函数对象中,这使它保持活动状态。它只在异步读取完成时启动回调时隐式转换为Connection*,并且对象至少在调用期间保持活动状态。如果handle_Receive没有从中创建另一个shared_ptr,并且存储在绑定仿函数中的shared_ptr是最后一个shared_ptr alive,则在回调返回后该对象将被销毁。