【问题标题】:How does enabled_shared_from_this affect the the life time of shared_ptr?enabled_shared_from_this 如何影响 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

【问题讨论】:

    标签: c++11 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_sp{new int};
        shared_ptr<int> good_sp{orig_sp};       // it's ok
        shared_ptr<int> bad_sp{orig_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>> 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&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 指向同一个原始指针就可以了。当您有一个底层引用计数对象,并且您想要创建一个指向它的智能引用计数指针,并且您不想编写或使用您自己的侵入式智能指针类时,这可能是一种有用的模式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-18
    • 1970-01-01
    • 2022-09-29
    • 2019-02-22
    • 2020-05-16
    • 1970-01-01
    • 2011-03-04
    相关资源
    最近更新 更多