我经常在代码中看到这种模式,将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
然后在返回的指针上调用成员函数是不够的,除非强指针在成员函数返回之前不被销毁。
答案 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
实例被销毁。Connection
的引用计数,并可能导致它被销毁。asnyc_
参数关联的函数将按顺序执行,而不是并发执行。其中包括io_handler_deallocate
和io_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,则在回调返回后该对象将被销毁。