异步编程时boost::bind中慎用this指针

2014年4月18日 由 Creater 留言 »

在类的成员里有时候实现了异步操作,异步的回调函数仍为该类的成员函数,比如下面。

    async_write(m_socket, boost::asio::buffer(msg.c_str(), msg.size()),
        bind(&Games::HandSend, this,
            boost::asio::placeholders::error));

上面这个例子是一个很常见的boost.asio的异步执行代码,如果没认真看很容易忽略了bind参数中的“this”参数,异步执行过程中另一个线程会持有这个”this”指针并在未知的时间里回调HandSend方法,而在持有这个指针的时间内,这个线程并不知道当前对象有可能已经被销毁,所以造成很低级的悬空指针。

正确的做法应该是:

    async_write(m_socket, boost::asio::buffer(msg.c_str(), msg.size()),
        bind(&Games::HandSend, shared_from_this(),
            boost::asio::placeholders::error)
    );

在asio中使用shared_from_this智能指针,该使用的时候必须使用,否则几乎肯定会发生问题。

来分析分析什么时候该用this,什么使用该用shared_from_this()。

1.使用this的地方举例

1.1示例链接

在主函数中定义了tcp_server对象,这个对象会一直存在栈中,而对于每次accept的连接,都是new出来的tcp_connection。

  void start_accept()
  {
    tcp_connection::pointer new_connection =
      tcp_connection::create(acceptor_.get_io_service());

    acceptor_.async_accept(new_connection->socket(),
        boost::bind(&tcp_server::handle_accept, this, new_connection,
          boost::asio::placeholders::error));
  }

这里使用了一个工厂方法隐藏了new的代码,在bind中使用了this,因为tcp_server对象存在栈中,而且tcp_server对象并不是一个局部变量,不用担心他会在其他地方被释放掉。

1.2示例链接

server(boost::asio::io_service& io_service, short port)
    : io_service_(io_service),
      acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
  {
    session_ptr new_session(new session(io_service_));
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  void handle_accept(session_ptr new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start();
    }

    new_session.reset(new session(io_service_));
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

类server中的两个函数和以上的解释一样。

2.未使用this的地方,使用share_from_this举例

以下的两个链接还是和上边两个链接分别相同,因为这两段程序中都使用了this与share_from_this,这能很好对比。

2.1示例链接,与1.1对比

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));
  }

tcp_server的bind中使用了this,而这个tcp_connection中使用share_from_this(),因为这个cp_connection对象是存在堆中,被new出来的。

 2.2示例链接,与1.2对比

void start()
  {
    using namespace std; // For time_t, time and ctime.
    time_t now = time(0);
    shared_const_buffer buffer(ctime(&now));
    boost::asio::async_write(socket_, buffer,
        boost::bind(&session::handle_write, shared_from_this()));
  }

server的bind中使用了this,而这个session中使用share_from_this(),因为这个session对象是存在堆中,被new出来的。

结论:

1.如果对象是存在于栈中则必须使用this,否则出现bad_weak_ptr异常。
2.如果对象存在于堆中则尽量使用share_from_this(),使用this的前提是你能保证这个对象一直存在(比如全局对象,主函数中具有全局生命的对象,静态对象)
广告位

评论已关闭.