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_ptr
的tcp_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_ptr
到tcp_connection
作为参数。魔术现在发生在 boost::bind
调用中,该调用用于指定对 async_accept
的回调。
该绑定调用返回一个函子对象,该对象存储调用handle_accept
按值所需的所有参数。因此,bind 返回的函子包含shared_ptr
到tcp_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_sp
与good_sp
共享引用计数,而与bad_sp
不共享引用计数。这意味着您不能从已经是 shared_ptr
ed 的原始指针创建 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_ptr
ed 对象的成员函数想要延长自己的生命周期。
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)
也是不可接受的。根据我们的第一个 sn-p,它将导致崩溃。
也许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_ptr 而不是 this
的成员提供了shared_from_this
。
【讨论】:
感谢您的回复。但它似乎没有回答这个问题。问题是如果 boost::asio::io_service 没有使用shared_ptr
来存储处理程序及其参数,那么所有shared_ptr
最终都会被销毁。所以它可能取决于 asio::io_service 处理程序实现的实现。
boost::asio::io_service
使用函子来保持回调,在这种情况下是 boost::bind
的返回值。函子存储shared_ptr<tcp_connection>
。删除functor时,shared_ptrshared_ptr
,而两者都希望指向 delete
指针。问题是两个ptr
s 认为他们可以delete
,而不是两个ptr
s。如果您有不同的销毁代码,则将两个 shared_ptr
指向同一个原始指针就可以了。当您有一个底层引用计数对象,并且您想要创建一个指向它的智能引用计数指针,并且您不想编写或使用您自己的侵入式智能指针类时,这可能是一种有用的模式。以上是关于enabled_shared_from_this 如何影响 shared_ptr 的生命周期?的主要内容,如果未能解决你的问题,请参考以下文章