2

我正在查看boost::asio 教程的一页

class tcp_server
{
public:

  tcp_server(boost::asio::io_service& io_service)
    : acceptor_(io_service, tcp::endpoint(tcp::v4(), 13))
  {
    start_accept();
  }

private:

  void start_accept()
  {
    tcp_connection::pointer new_connection =
      tcp_connection::create(acceptor_.get_io_service()); // shared_ptr got created.
    acceptor_.async_accept(new_connection->socket(),
      boost::bind(&tcp_server::handle_accept, this, new_connection,
      boost::asio::placeholders::error)); // instance added to io_service task list, but bind does not use shared_ptr internally I believe.
  } // shared_ptr of tcp_connection goes out of scope.

  void handle_accept(tcp_connection::pointer new_connection,
  const boost::system::error_code& error)
  {
    if (!error)
    {
      new_connection->start();
    }
    start_accept();
  }

class tcp_connection
  : public boost::enable_shared_from_this<tcp_connection>
{
public:
  typedef boost::shared_ptr<tcp_connection> pointer;

  static pointer create(boost::asio::io_service& io_service)
  {
    return pointer(new tcp_connection(io_service));
  }

  tcp::socket& socket()
  {
    return socket_;
  }

  void start()
  {
    message_ = make_daytime_string();
    boost::asio::async_write(socket_, boost::asio::buffer(message_),
      boost::bind(&tcp_connection::handle_write, shared_from_this(),
      boost::asio::placeholders::error,
      boost::asio::placeholders::bytes_transferred));
  }

private:
  tcp_connection(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
  }

  void handle_write(const boost::system::error_code& /*error*/,
      size_t /*bytes_transferred*/)
  {
  }

  tcp::socket socket_;
  std::string message_;
};

我发现有一部分运行时间没有shared_ptr对象的tcp_connection对象是活着的。这似乎意味着该tcp_connection对象将在该部分的开头被销毁,因为它的计数shared_ptr下降到零,这显然不是我们想要的。

但是后来我看到了课堂tcp_connection引用中的评论

我们将使用 shared_ptr 和 enable_shared_from_this 因为我们希望 tcp_connection 对象只要有引用它的操作就保持活动状态。

我也搜索了这个问题,并在 SO here中得到了一个问答。但是我仍然对标题问题感到困惑。具体是什么意思there is an operation that refers to it?在tcp_server::start_accept()返回时,所有实例shared_ptrtcp_connection应该超出范围,并且可能只有一些原始指针引用被添加到io_service任务列表中。当没有该对象的实例时,如何enabled_shared_from_this防止堆实例被破坏?或者它与 enabled_shared_from_this 无关,而是内部有界 async_handler的保持?tcp_connectionshared_ptrtcp_connectionboost::asio::io_serviceshared_ptr

4

2 回答 2

3

仿函数使对象保持活动状态。

请注意handle_accept回调函数如何将shared_ptrtotcp_connection作为参数。魔术现在发生在boost::bind用于指定回调的调用中async_accept

handle_accept 该绑定调用返回一个函子对象,该对象存储按值调用所需的所有参数。shared_ptr因此 bind 返回的函子包含to的副本,tcp_connection从而使其保持活动状态。仿函数(将其视为 a boost::function)现在依次由 复制,async_accept以允许io_service在接受操作完成后执行回调。

所以所有权链是:io_service拥有函子,拥有的shared_ptr,拥有的tcp_connection。顺便说一句,同样的事情也有效enable_shared_from_this

于 2013-10-16T08:00:45.507 回答
1

这个问题可以通过描述 shared_ptr 的非侵入式设计来回答,这意味着不接触其指向对象的“任何”代码。

我们可以从它的非侵入式设计中获得很多优点。但另一方面,也不可避免地导致了这样的情况:一旦shared_ptr,shared_ptr到处都没有原始指针。

让我们看看这个片段:

{
    shared_ptr<int> orig_sp{new int};
    shared_ptr<int> good_sp{orig_sp};       // it's ok
    shared_ptr<int> bad_sp{orig_sp.get()};  // leads crashing
}

这个片段会使你的程序崩溃,因为对同一个原始指针(new int)调用了两次删除。根本原因是orig_sp与 while 共享引用计数good_sp不与bad_sp. 这意味着您不能从已经被shared_ptr编辑的原始指针创建 shared_ptr 。

然后回到 ASIO 示例代码。让比's的生命周期new_connection更长已经是众所周知的了。tcp_server::start_accept()在这种情况下,shared_ptr 是一个非常有用的模板,可以准确地延长生命周期。

void handler(shared_ptr<vector<char>> buffer, /* other declarations */)
{
    // works after write.
    // buffer will be deleted automatically.
}

int main()
{
    shared_ptr<vector<char>> buffer{new vector<char>};

    // preparing buffer

    boost::asio::async_write(socket_, boost::asio::buffer(*buffer),
      boost::bind(handler, buffer, boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred));
}

但问题是shared_ptred对象的成员函数想要延长自己的生命周期。

class tcp_connection {
    void start()
    {
        boost::asio::async_write(socket_, boost::asio::buffer(message_),
          boost::bind(&tcp_connection::handle_write, (/* how to fill this field? */),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
    }
};
  • 填写this该字段是不可接受的,因为它不会延长 tcp_connection 的生命周期。
  • 填充boost::shared_ptr<tcp_connection>(this)也是不可接受的。根据我们的第一个片段,它将导致崩溃。
  • 也许boost::shared_ptr<tcp_connection>(tcp_connection::create(acceptor_.get_io_service()))看起来不错,但它试图在其成员中创建对象本身。

现在很清楚了。tcp_connection::start()想知道管理的 shared_ptr,但根据 shared_ptr 的非侵入式设计,tcp_connection不应该包含 shared_ptr 的任何信息。这是不可能的任务。

最后,我们不得不妥协,寻求enable_shared_from_this帮助。enable_shared_from_this要求必须使用CRTP idiom从它派生该类。这就是为什么tcp_connection看起来如此棘手。

class tcp_connection : boost::enable_shared_from_this<tcp_connection> {
    void start()
    {
        // ...
        boost::asio::async_write(socket_, boost::asio::buffer(message_),
          boost::bind(&tcp_connection::handle_write, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
    }
};

enable_shared_from_this模板为 shared_ptr 保留一些字段,例如引用计数,并shared_from_this为那些成员函数想要使用 shared_ptr 而不是this.

于 2013-10-16T07:52:52.263 回答