boost::weak_ptr和enable_shared_from_this

本文详细解析了shared_ptr与weak_ptr的区别及使用场景,通过实例展示了weak_ptr在解决递归依赖关系、避免共享资源时的用法,并讨论了其在实践中的应用与限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

boost::weak_ptr 和 enable_shared_from_this

 

shared_ptr 在我的实践中使用很广 , 在接口层面上 , 我基本都会默认的使用 shared_ptr. 而 weak_ptr 则很少使用 ; 即便使用 , 也是间接的使用 , 例如使得 class A 继承自boost::enable_shared_from_this, 这样 A 便具有了一个 weak_ptr 的成员对象 , 我便可以通过接口 shared_from_this() 得到一个 boost::shared_ptr<A> 的对象 .

除此之外 , 在实践中我几乎没有用到 weak_ptr. 到目前为止 , 我见到的对 weak_ptr 论述最好的是 << Beyond.the.C.plus.plus.Standard.Library.An.Introduction.to.Boost>>. 本书给出了使用 weak_ptr 的三种场景 : 打破递归的依赖关系 ; 使用一个共享的资源而不需要共享所有权 ; 避免悬空的指针 .

后面两种场景相对比较容易明白 , 对于第一种则相对模糊 . 最近几天我相对仔细的研究了一下 shared_ptr 和 weak_ptr 的源代码 , 给出我自己的理解如下 .

 

weak_ptr 是依附于 shared_ptr 的 , 所以首先要把 shared_ptr 弄清楚 ; 另外 , 我认为 boost::enable_shared_from_this 是解释递归的依赖关系的最好例子 , 所以使用这个实例解释weak_ptr 最好不过了 .

 

shared_ptr 的图解

 

 

 

 

    如图所示 ,3 个智能指针对象 sp1,sp2,sp3, 共享同一个对象 object 和同一个计数器 counter, 当 3 个指针对象 sp1,sp2,sp3 声明周期结束的时候 ,counter==0,object 会被销毁 ( 当然, 这个销毁器本身可以由你定制 ).

 

shared_ptr 的一个很大的特点是 :counter==0,object 会被销毁 ; 反之 , 如果 counter 总是不为 0, 那么 object 会成为永久对象 , 例如全局对象 .

 

在 http://www.boost.org/doc/libs/1_35_0/libs/smart_ptr/sp_techniques.html 中给出了一个从 this 获得 shared_ptr 的实例 , 答案是使用 weak_ptr, 问题是为什么是 weak_ptr.

 

让我们把这个问题重新说明一下 :

struct X

{

         boost::shared_ptr<X> getX()

{

         boost::shared_ptr<X> r ;//???? 如何实现

         return r;

}

};

 

要得到 X 的智能指针 , 只是在对象指针是受 shared_ptr 保护的基础上的 , 举例如下 :

void test_X()

{

         {

X x;

                  boost::shared_ptr<X> px = x.getX();// 错误

}

         {

X* x = new X();

boost::shared_ptr<X> px = x->getX();// 错误

}

         {

boost::shared_ptr<X>   x (new X());

boost::shared_ptr<X> px = x->getX();// 正确

}

}

我们再回到原来的问题上 , 如何实现 X::getX() 函数 . 要构造 X 的智能指针 , 需要知道两个信息 ,object 的指针和 object 的计数器 , 显然 , 指针本身可以通过 this 获得 , 问题是如何获得计数器 , 也即上面图中的 counter. 很显然 , 我们必须在 X 上存储某些信息来得到计数器 .

最先想到的也许是使用一个指向 counter 的指针存储在 X 上面 , 这样应该是可以的 , 但是存在一个问题 : 暴露了 shared_ptr 的实现细节 , 虽然 shared_ptr 是基于计数器的 , 但是这不属于用户需要知道的事情 . 最好的方式是让 shared_ptr 本身来充当这个角色 , 这样所有的实现细节就都封装在 shared_ptr 内部了 .

经过这样改装的 shared_ptr 的图解是 :

 
 
 

  

     经过这样包装的 object, 问题立刻暴露出来 : 循环引用 , 造成 object 对象无法释放 , 成为 ” 全局对象 ”. 很显然 , 问题在于 object 本身参与了引用计数 , 也就是所有权的分享 . 我们需要的是不同于 shared_ptr 的这样的一类共享指针 : 他们并不参与对象的所有权 , 只是能够观察到对象的所有权 . 而这正是 weak_ptr 的本质所在 .

 

当然了 ,weak_ptr 还有其他的特征 : 查看对象指针是否过期 ( 这也是比裸指针好的地方 ).

循环引用的情况很罕见 , 这可能也是我在实际中很少使用 weak_ptr 的原因吧 .

<think>我们有一个使用Boost.AsioBoost.Beast的异步接受操作。具体代码是: acceptor_->async_accept( boost::asio::make_strand(*ioc_), boost::beast::bind_front_handler( &MainSlaveRpcServerImpl::OnAccept, shared_from_this())); 这段代码的作用是异步地接受一个传入的连接。我们逐部分解释: 1. `acceptor_->async_accept`: 这是Boost.Asio中TCP接收器的异步接受操作。它开始一个异步操作来接受一个新的连接。 2. `boost::asio::make_strand(*ioc_)`: 这里创建了一个strand对象。Strand用于确保在同一个strand中分发的多个完成处理程序不会并发执行。这对于避免并发问题(如数据竞争)很有用。`ioc_`是一个指向`io_context`的指针(或可能是`io_context`对象),`*ioc_`获取其引用。`make_strand`从`io_context`创建一个strand。 3. `boost::beast::bind_front_handler`: 这是Boost.Beast提供的一个工具函数,用于将成员函数绑定为异步操作的处理程序。它类似于`std::bind`,但专门设计用于与Boost.AsioBoost.Beast的完成处理程序一起工作,并且支持处理程序跟踪(handler tracking)。它绑定成员函数的第一个参数(通常是`this`指针)后续参数(如果有的话)。 4. `&MainSlaveRpcServerImpl::OnAccept`: 这是处理接受连接的回调函数。当有新连接被接受时,这个函数将被调用。它应该是类的成员函数,其签名可能类似于: `void OnAccept(boost::beast::error_code ec, boost::asio::ip::tcp::socket socket);` 5. `shared_from_this()`: 这用于获取当前对象的`std::shared_ptr`。这要求当前对象是由`std::shared_ptr`管理的,并且必须继承自`std::enable_shared_from_this`。使用`shared_from_this`可以确保当处理程序被调用时,对象仍然存在(因为处理程序持有一个`shared_ptr`,从而延长了对象的生命周期)。 注意:使用`shared_from_this()`时,必须确保在调用`shared_from_this()`之前,对象已经被`std::shared_ptr`所管理。否则,会抛出`std::bad_weak_ptr`异常。 因此,这段代码的整体含义是:开始一个异步接受操作,当有新的连接到来时,将其关联到由`ioc_`创建的strand上,然后使用`MainSlaveRpcServerImpl::OnAccept`作为处理程序。同时,通过`shared_from_this()`确保在处理程序被调用时,`MainSlaveRpcServerImpl`对象仍然存在。 现在,我们给出一个完整的示例,展示如何在一个类中实现上述代码: ```cpp #include <boost/asio.hpp> #include <boost/beast.hpp> #include <memory> namespace net = boost::asio; namespace beast = boost::beast; class MainSlaveRpcServerImpl : public std::enable_shared_from_this<MainSlaveRpcServerImpl> { public: MainSlaveRpcServerImpl(net::io_context& ioc) : ioc_(ioc), acceptor_(std::make_shared<net::ip::tcp::acceptor>(ioc, net::ip::tcp::endpoint(net::ip::tcp::v4(), 8080))) { StartAccept(); } void StartAccept() { acceptor_->async_accept( net::make_strand(ioc_), beast::bind_front_handler( &MainSlaveRpcServerImpl::OnAccept, shared_from_this())); } void OnAccept(beast::error_code ec, net::ip::tcp::socket socket) { if (ec) { // 处理错误 return; } // 处理新连接,例如:创建一个会话并开始读取 // ... // 再次开始接受下一个连接 StartAccept(); } private: net::io_context& ioc_; std::shared_ptr<net::ip::tcp::acceptor> acceptor_; }; int main() { net::io_context ioc; auto server = std::make_shared<MainSlaveRpcServerImpl>(ioc); ioc.run(); return 0; } ``` 注意:上面的代码中,`acceptor_`被声明为`std::shared_ptr`,这是因为在多个异步操作中可能需要共享这个接收器。另外,在`OnAccept`中,当处理完一个连接后,再次调用`StartAccept()`以开始接受下一个连接。 但是,请注意,如果`OnAccept`中发生了异常,或者多次调用`StartAccept()`,可能会导致问题。因此,需要确保错误处理得当。 另外,上面代码中,`net::io_context`对象`ioc`在`main`函数中创建,并传递给`MainSlaveRpcServerImpl`的构造函数。然后,我们创建了一个`MainSlaveRpcServerImpl`的共享指针,并运行`io_context`。 现在,根据要求,我们需要在最后提出相关问题: §§相关问题§§: 1. 在Boost.Asio中,strand的作用是什么? 2. 为什么在异步操作中使用`shared_from_this()`? 3. 在`OnAccept`函数中,如果处理新连接时发生异常,会有什么后果?如何避免? 4. 如何修改上述代码以支持SSL连接?</think>### 异步接受连接代码分析 #### 代码解释: ```cpp acceptor_->async_accept( boost::asio::make_strand(*ioc_), // 创建执行strand boost::beast::bind_front_handler( // 绑定处理函数 &MainSlaveRpcServerImpl::OnAccept, shared_from_this())); // 确保对象生命周期 ``` 1. **`async_accept`** - Boost.Asio的异步接受新连接操作 - 当有新连接到达时触发回调 2. **`make_strand(*ioc_)`** - 创建关联到`io_context`的strand对象 - **作用**:确保异步操作的完成处理程序在strand中执行,避免并发访问共享数据时的竞争条件 - 相当于隐式同步机制(无需显式锁) 3. **`bind_front_handler`** - Boost.Beast提供的处理程序绑定工具 - **优势**:自动适配Asio处理程序签名,支持移动语义 - 等价于: ```cpp [self = shared_from_this()](auto&&... args) { self->OnAccept(std::forward<decltype(args)>(args)...); } ``` 4. **`shared_from_this()`** - 关键生命周期管理技术 - **要求**:类必须继承`enable_shared_from_this` - **作用**:让处理程序持有对象的shared_ptr,防止异步操作期间对象被意外销毁 #### 完整实现示例: ```cpp #include <boost/asio.hpp> #include <boost/beast.hpp> #include <memory> namespace net = boost::asio; namespace beast = boost::beast; class MainSlaveRpcServerImpl : public std::enable_shared_from_this<MainSlaveRpcServerImpl> { public: MainSlaveRpcServerImpl(net::io_context& ioc) : ioc_(ioc), acceptor_(ioc, net::ip::tcp::endpoint(net::ip::tcp::v4(), 8080)) { start_accept(); } private: void start_accept() { acceptor_.async_accept( net::make_strand(ioc_), beast::bind_front_handler( &MainSlaveRpcServerImpl::on_accept, shared_from_this())); } void on_accept(beast::error_code ec, net::ip::tcp::socket socket) { if (ec) { // 处理错误: ec.message() return; } // 处理新连接 (启动会话等) // ... // 继续接受下一个连接 start_accept(); } net::io_context& ioc_; net::ip::tcp::acceptor acceptor_; }; // 使用示例 int main() { net::io_context ioc; auto server = std::make_shared<MainSlaveRpcServerImpl>(ioc); ioc.run(); } ``` #### 关键注意事项: 1. **strand的作用** - 当`io_context`在多线程中运行时,strand保证: - 同一strand的处理程序不会并发执行 - 不同strand的处理程序可能并行执行 2. **生命周期管理** - 必须通过`shared_ptr`创建对象: ```cpp auto server = std::make_shared<MainSlaveRpcServerImpl>(ioc); ``` - 构造函数中禁止调用`shared_from_this()`(对象未完全构造) 3. **错误处理** - 必须检查`on_accept`的`error_code`参数 - 典型错误:`operation_aborted`(acceptor关闭时发生)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值