enabled_shared_from_this 如何影响 shared_ptr 的生命周期?

Posted

技术标签:

【中文标题】enabled_shared_from_this 如何影响 shared_ptr 的生命周期?【英文标题】:How does enabled_shared_from_this affect the the life time of shared_ptr? 【发布时间】:2013-10-16 02:51:41 【问题描述】:

我在看一个page 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 任务列表中。当这个tcp_connection 对象没有shared_ptr 的实例时,enabled_shared_from_this 如何防止tcp_connection 的堆实例被销毁?或者它与enabled_shared_from_this无关,但boost::asio::io_service在内部保留有界async_handler的shared_ptr

【问题讨论】:

【参考方案1】:

函子使对象保持活动状态。

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

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

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

【讨论】:

嗯,看来关键是bind将参数“按值”存储到函数对象中。【参考方案2】:

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

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

让我们看看这个sn-p:


    shared_ptr<int> orig_spnew int;
    shared_ptr<int> good_sporig_sp;       // it's ok
    shared_ptr<int> bad_sporig_sp.get();  // leads crashing

这个 sn-p 会使你的程序崩溃,因为对同一个原始指针 (new int) 调用了两次删除。根本原因是orig_spgood_sp 共享引用计数,而与bad_sp 不共享引用计数。这意味着您不能从已经是 shared_ptred 的原始指针创建 shared_ptr。

然后回到 ASIO 示例代码。众所周知,使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>> buffernew 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&lt;tcp_connection&gt;(this) 也是不可接受的。根据我们的第一个 sn-p,它将导致崩溃。 也许boost::shared_ptr&lt;tcp_connection&gt;(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_ptr 而不是 this 的成员提供了shared_from_this

【讨论】:

感谢您的回复。但它似乎没有回答这个问题。问题是如果 boost::asio::io_service 没有使用shared_ptr 来存储处理程序及其参数,那么所有shared_ptr 最终都会被销毁。所以它可能取决于 asio::io_service 处理程序实现的实现。 boost::asio::io_service 使用函子来保持回调,在这种情况下是 boost::bind 的返回值。函子存储shared_ptr&lt;tcp_connection&gt;。删除functor时,shared_ptr`也会被删除。 好的,我现在知道你真正的需求了。对不起,无聊的答案。我认为this post 可能适合你。 是的,那篇文章有帮助。谢谢! "你不能从一个已经是 shared_ptred 的原始指针创建一个 shared_ptr。" - 错误的。您不能为同一个原始指针创建两个 shared_ptr,而两者都希望指向 delete 指针。问题是两个ptrs 认为他们可以delete,而不是两个ptrs。如果您有不同的销毁代码,则将两个 shared_ptr 指向同一个原始指针就可以了。当您有一个底层引用计数对象,并且您想要创建一个指向它的智能引用计数指针,并且您不想编写或使用您自己的侵入式智能指针类时,这可能是一种有用的模式。

以上是关于enabled_shared_from_this 如何影响 shared_ptr 的生命周期?的主要内容,如果未能解决你的问题,请参考以下文章

enable_shared_from_this用法分析