shared_ptr使用问题解惑:生命周期、参数传递及Boost.Asio适配
问题根源与解答
咱们先从你遇到的std::bad_weak_ptr异常说起:你把StartSession()的调用移到了Session的构造函数里,这直接触发了错误——std::enable_shared_from_this::shared_from_this()只能在对象已经被至少一个std::shared_ptr托管之后调用。当你在构造函数里调用它时,std::make_shared<Session>还没完成对象的构造,enable_shared_from_this内部用来跟踪对象的weak_ptr还没被初始化,自然会抛出异常。
接下来逐个解答你的疑问:
1. 如何正确使用shared_ptr?在这类场景中何时创建、销毁,存储位置在哪?
- 创建时机:当你的对象需要被异步操作(比如Boost.Asio的
async_write/async_read回调)持有,或者需要跨作用域保证生命周期时,用std::make_shared(或std::shared_ptr)创建。像你原代码里的std::make_shared<Session>(std::move(m_oSocket))->StartSession()就很正确:先创建托管对象的shared_ptr,再调用成员函数,此时shared_from_this()是安全的。 - 销毁时机:
shared_ptr是引用计数式的智能指针,当所有指向对象的shared_ptr(包括你在回调里捕获的self)都被销毁或重置时,对象会自动被析构。在Asio的场景里,只要回调还没执行完,self就会保持对象存活;当异步操作完成(成功或出错),回调执行完毕后,self被销毁,引用计数减到0,Session对象就会被自动删除。 - 存储位置:用
make_shared创建的对象会在堆上分配,shared_ptr本身可以存在栈上(比如回调里的self),但它管理的对象始终在堆上。你不需要手动存储Session的shared_ptr,因为Asio的异步回调会通过捕获self来维持对象的生命周期,直到操作结束。
2. 何时使用this,何时使用shared_from_this()?有无简单准则或教程?
这里有个非常明确的准则:
- 用
this的场景:在对象的同步成员函数内部,调用其他成员函数或者访问成员变量时直接用this(甚至可以省略)。因为此时你确定对象是存活的——毕竟你正在调用它的成员函数,外部肯定有一个shared_ptr或原始指针持有它。 - 用
shared_from_this()的场景:当你需要把对象的所有权传递给异步操作(比如Asio的回调),或者需要确保对象在某个异步任务完成前不会被销毁时,必须调用shared_from_this()获取一个shared_ptr,并把这个shared_ptr捕获到lambda或bind的参数里。这样异步操作的回调会持有这个shared_ptr,保证对象在回调执行期间存活。
绝对禁忌:永远不要在构造函数或析构函数里调用shared_from_this()——构造时对象还没被shared_ptr托管,析构时引用计数已经在归零的过程中,这两种情况都会导致bad_weak_ptr异常。
3. lambda表达式与boost::bind的区别是什么?为何有人用前者有人用后者?
这俩本质上都是用来适配函数参数、创建可调用对象的工具,但适用场景和语法差异很大:
- lambda表达式:是C++11及以后的标准特性,优势非常明显:
- 可以直接在代码里写内联的回调逻辑,可读性极强,不用单独写一个成员函数或全局函数。
- 捕获变量的方式非常灵活(值捕获、引用捕获、捕获特定变量等),能清晰控制变量的生命周期。
- 支持泛型、可变参数,功能比
boost::bind强大得多。
现在新写的Asio代码几乎都会用lambda,因为它更简洁、更直观。
- boost::bind:是C11之前的“替代品”,用来解决当时标准库没有lambda的问题。它的作用是把多参数的函数绑定成少参数的,或者固定某些参数的值。比如在C11之前,Asio的回调会用
boost::bind(&Session::OnWrite, this, boost::asio::placeholders::error, ...)来绑定成员函数。
现在它的主要用途是维护旧代码,或者在一些不能用C++11及以上标准的环境中使用。
简单说:lambda是现代C++的首选,boost::bind是旧时代的遗留产物,除非维护老代码,否则没必要用它。
内容的提问来源于stack exchange,提问作者Martin Herrmann
相关产品推荐
相关产品推荐

