结合io_context使用enable_shared_from_this的循环引用疑问及解决
你的理解是否正确?
完全正确,这里确实存在循环引用链:Test 对象持有 shared_ptr<io_context>(成员ioContext_),io_context 的任务队列中保存着捕获了 Test 对象强引用(shared_from_this())的lambda,而lambda又持有 Test 的强引用,最终形成 Test → io_context → lambda → Test 的循环。
当main中的myTest释放后,Test的引用计数因lambda的捕获仍不为0,io_context的引用计数因Test的持有也不为0,两者都无法被正确销毁,会造成内存泄漏。
避免循环引用的方法
1. 用weak_ptr捕获Test对象,打破强引用循环
在lambda中捕获Test的weak_ptr而非shared_ptr,需要操作对象时再升级为强引用,这样不会增加Test的引用计数,直接切断循环链:
修改start()方法的代码如下:
void start() { std::cout << getTime() << "at start\n"; timer_.expires_after(std::chrono::seconds(3)); std::weak_ptr<Test> weak_self = shared_from_this(); timer_.async_wait([this, weak_self, io_ctx=ioContext_](const boost::system::error_code& ec) { std::cout << getTime() << "Inside wait!\n"; if (!ec) { io_ctx->post([weak_self]() { if (auto self = weak_self.lock()) { self->printInPost(); } }); } }); }
这里通过weak_self捕获弱引用,在post的lambda中先调用lock()获取强引用:如果Test对象已被销毁,lock()会返回空指针,此时不会执行后续操作,既避免了循环引用,也保证了操作的安全性。
2. 将Test持有的io_context改为weak_ptr
如果Test不需要延长io_context的生命周期,可以把成员变量ioContext_从shared_ptr改为weak_ptr,这样Test不会增加io_context的引用计数,直接打破循环:
修改Test类的相关代码:
class Test : public std::enable_shared_from_this<Test> { public: Test(const std::shared_ptr<boost::asio::io_context>& ioContext) : ioContext_(ioContext), timer_(*ioContext) {} void start() { std::cout << getTime() << "at start\n"; timer_.expires_after(std::chrono::seconds(3)); timer_.async_wait([this, self=shared_from_this()](const boost::system::error_code& ec) { std::cout << getTime() << "Inside wait!\n"; if (!ec) { if (auto io_ctx = ioContext_.lock()) { io_ctx->post([self]() { self->printInPost(); }); } } }); } // 其他方法保持不变 private: std::weak_ptr<boost::asio::io_context> ioContext_; // 改为weak_ptr boost::asio::steady_timer timer_; };
这种方式下,Test不再持有io_context的强引用,当main中的ioContext被销毁后,lock()会失败,不会执行post操作,从根源上避免了循环引用。
3. 确保lambda执行后及时释放强引用(不推荐)
如果业务逻辑能严格保证lambda执行时Test对象一定存在,可以在lambda中不捕获shared_from_this(),直接使用this指针。但这种方式风险极高,一旦Test对象提前销毁,会导致未定义行为,仅在极端场景下考虑使用。
内容的提问来源于stack exchange,提问作者proton

