boost::weak_ptr和enable_shared_from_this

本文详细解析了boost::weak_ptr和enable_shared_from_this的使用场景及原理,通过图解展示了shared_ptr的工作机制,并阐述了在特定情况下使用weak_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的图解

object

counter

Object_ptr

counter_ptr

Object_ptr

counter_ptr

Object_ptr

counter_ptr

<!--[if !vml]--><!--[endif]--> 

 

 

 

    如图所示,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的图解是:

counter

Object_ptr

counter_ptr

Object_ptr

counter_ptr

Object_ptr

counter_ptr

object

Object_ptr

counter_ptr

<!--[if !vml]-->
 
 
 
<!--[endif]-->  

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

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

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

<think>我们正在实现一个继承自std::enable_shared_from_this的Fiber类。Fiber(纤程)是一种轻量级的线程,由用户自己进行调度。在C++中,我们通常使用第三方库(如Boost.Fiber)来实现纤程,但这里我们尝试自己实现一个简单的版本。由于Fiber对象需要被多个地方共享(例如在调度器中、在等待的纤程中等),因此我们使用std::shared_ptr来管理其生命周期。为了让Fiber对象能够安全地获取自身的shared_ptr,我们让它继承自std::enable_shared_from_this。注意:使用std::enable_shared_from_this时,必须确保对象已经被std::shared_ptr管理。也就是说,不能直接在栈上创建该对象,而应该通过std::make_shared等函数创建。以下是一个简单的Fiber类的实现框架:```cpp#include<memory>#include<functional>#include<coroutine>//假设使用C++20的协程(这里仅作为示例,实际上Fiber可以使用协程,但也可以用其他方式实现)//我们使用C++20的协程特性来实现纤程的执行体(需要编译器支持)//注意:这里仅是一个简化示例,实际实现需要考虑上下文切换、调度等复杂问题classFiber:publicstd::enable_shared_from_this<Fiber>{public://纤程的状态enumclassState{Ready,Running,Suspended,Terminated};//构造函数,传入一个可调用对象(纤程要执行的函数)template<typenameFn>explicitFiber(Fn&&fn):func_(std::forward<Fn>(fn)){}//启动纤程voidstart(){state_=State::Running;//这里我们假设使用协程的resume来启动(简化)//实际中可能需要保存/恢复上下文func_();}//暂停当前纤程(让出执行权)voidyield(){state_=State::Suspended;//实际中需要切换到调度器上下文}//获取当前纤程的shared_ptrstd::shared_ptr<Fiber>get_shared(){returnshared_from_this();//从enable_shared_from_this继承而来}Statestate()const{returnstate_;}private:std::function<void()>func_;Statestate_=State::Ready;};//使用示例intmain(){//创建纤程,注意必须使用shared_ptr管理autofiber=std::make_shared<Fiber>([]{std::cout<<"Fiberstarted\n";//模拟工作//...//让出执行权//注意:在纤程内部如何获取自己的shared_ptr?可以通过全局调度器或捕获//但是,在纤程函数内部,我们不能直接使用shared_from_this(),因为this可能不是Fiber对象(如果lambda捕获了this,但这里没有捕获)//因此,我们需要设计使得纤程函数能够访问到它所属的Fiber对象。});//启动纤程fiber->start();//...其他代码}```然而,上面的示例中有一个问题:在纤程执行的函数(lambda)内部,我们无法直接调用yield(),因为yield()是Fiber的成员函数,而lambda没有捕获Fiber对象。为了解决这个问题,我们可以在构造Fiber时,将自身的shared_ptr传递给执行函数。修改如下:```cppclassFiber:publicstd::enable_shared_from_this<Fiber>{public://...状态枚举同上template<typenameFn>explicitFiber(Fn&&fn):func_(std::forward<Fn>(fn)){}voidstart(){state_=State::Running;//将当前Fiber的shared_ptr作为参数传递给执行函数func_(shared_from_this());}//...其他同上};//使用示例intmain(){autofiber=std::make_shared<Fiber>([](std::shared_ptr<Fiber>self){std::cout<<"Fiberstarted\n";//现在可以通过self来调用yieldself->yield();});fiber->start();}```注意:在构造函数中,我们不能调用shared_from_this(),因为此时对象还没有被shared_ptr管理(在make_shared之后才被管理)。所以我们在start()中调用shared_from_this(),并传递给执行函数。但是,上面的代码在构造Fiber时,我们传递的lambda捕获了一个shared_ptr(即self),这会导致循环引用:fiber对象持有lambda(通过func_),而lambda又持有fiber的shared_ptr(self)。这样即使外部不再持有fiber的shared_ptr,fiber也不会被释放。因此,我们需要小心地设计。为了避免循环引用,我们可以将执行函数中的self保存为weak_ptr,或者确保在不需要的时候打破循环。另外,我们可以考虑在Fiber执行结束时,将func_置空,从而打破循环。另一种设计是不传递shared_ptr给执行函数,而是提供一个全局的调度器接口,让执行函数通过调度器接口来让出,而调度器知道当前的Fiber。这样执行函数就不需要持有Fiber的shared_ptr。由于Fiber的实现是一个复杂的话题,这里我们只关注如何使用std::enable_shared_from_this。我们选择传递weak_ptr给执行函数,以避免循环引用:```cppclassFiber:publicstd::enable_shared_from_this<Fiber>{public:template<typenameFn>explicitFiber(Fn&&fn):func_(std::forward<Fn>(fn)){}voidstart(){state_=State::Running;//传递weak_ptrfunc_(weak_from_this());}voidyield(){state_=State::Suspended;//...}//提供一个从weak_ptr升级并调用yield的静态方法,方便在lambda中使用staticvoidyield(conststd::weak_ptr<Fiber>&weak_self){if(autoself=weak_self.lock()){self->yield();}}private:std::function<void(std::weak_ptr<Fiber>)>func_;Statestate_=State::Ready;};//使用示例intmain(){autofiber=std::make_shared<Fiber>([](std::weak_ptr<Fiber>weak_self){std::cout<<"Fiberstarted\n";Fiber::yield(weak_self);//通过静态方法让出});fiber->start();}```这样,执行函数中持有的是weak_ptr,不会增加引用计数,从而避免了循环引用。总结:1.Fiber类继承std::enable_shared_from_this,以便在成员函数中获取自身的shared_ptr。2.在Fiber的执行函数中,我们传递weak_ptr,以避免循环引用。3.提供静态工具函数来通过weak_ptr调用成员函数(如yield)。注意:以上代码仅为示例,并未实现真正的纤程上下文切换。实际中,上下文切换需要使用平台相关的API(如Windows的FiberAPI,或ucontext,或使用汇编代码保存寄存器等)或者使用C++20协程(协程有独立的栈,但需要编译器支持)。另外,关于std::enable_shared_from_this的使用,必须注意对象必须由shared_ptr管理,且在调用shared_from_this()之前,shared_ptr必须已经存在。否则会抛出std::bad_weak_ptr异常。引用说明:-在实现中,我们使用了std::enable_shared_from_this,其原理是在对象内部存储一个weak_ptr,该weak_ptrshared_ptr构造时被初始化[^2][^3]。-因此,直接构造Fiber对象(不使用shared_ptr)或者在没有shared_ptr管理的情况下调用shared_from_this(),都会导致错误。最后,关于Fiber类的使用,我们还需要注意线程安全等问题,这里不再展开。§§相关问题§§1.如何避免使用std::enable_shared_from_this时的循环引用?2.在C++中实现纤程有哪些常用的方法?3.如何使用C++20协程来实现纤程?4.为什么在构造函数中不能调用shared_from_this()?</think>### 实现继承自 std::enable_shared_from_this 的 Fiber 类 在 C++ 中实现纤程(Fiber)并继承 `std::enable_shared_from_this` 需要解决两个核心问题: 1. **纤程的生命周期管理**(通过共享指针实现) 2. **安全的自身指针获取**(通过 `shared_from_this()` 实现) #### 基础实现代码 ```cpp #include <memory> #include <functional> class Fiber : public std::enable_shared_from_this<Fiber> { public: using Ptr = std::shared_ptr<Fiber>; // 构造函数必须私有化,强制使用create方法创建 template<typename Callable> static Ptr create(Callable&& func) { // 使用make_shared初始化enable_shared_from_this内部的weak_ptr auto fiber = std::make_shared<Fiber>(std::forward<Callable>(func)); fiber->init(); // 初始化后执行 return fiber; } void resume() { // 通过共享指针安全访问 if (auto self = weak_self_.lock()) { func_(self); } } private: template<typename Callable> Fiber(Callable&& func) : func_(std::forward<Callable>(func)) {} void init() { weak_self_ = weak_from_this(); // 初始化弱引用 } std::function<void(Ptr)> func_; std::weak_ptr<Fiber> weak_self_; // 关键:避免循环引用 }; ``` #### 关键设计要点 1. **安全创建模式** - 构造函数设为私有 - 通过静态工厂方法 `create()` 创建对象 - 确保对象始终由 `shared_ptr` 管理(满足 `enable_shared_from_this` 的前提条件)[^2][^3] 2. **循环引用处理** - 存储 `weak_ptr` 而非 `shared_ptr` 作为成员变量 - 在 `init()` 中初始化 `weak_self_ = weak_from_this()` - 使用前通过 `lock()` 升级为临时 `shared_ptr` 3. **执行流程** ```cpp auto fiber = Fiber::create([](Fiber::Ptr self) { std::cout << "Fiber running!"; // 可通过self访问纤程成员 }); fiber->resume(); // 执行纤程 ``` #### 典型使用场景 1. **纤程调度器** ```cpp class Scheduler { std::queue<Fiber::Ptr> ready_queue; void schedule(Fiber::Ptr fiber) { ready_queue.push(fiber); } void run() { while (!ready_queue.empty()) { auto fiber = ready_queue.front(); ready_queue.pop(); fiber->resume(); } } }; ``` 2. **纤程间通信** ```cpp void fiber_a(Fiber::Ptr self) { auto fiber_b = Fiber::create(...); // 将自身weak_ptr传递给其他纤程 fiber_b->set_caller(self->weak_from_this()); } ``` #### 注意事项 1. **禁止栈上对象** ```cpp Fiber fiber(...); // 错误!违反enable_shared_from_this约定 fiber.resume(); // 触发未定义行为 ``` 2. **首次指针获取时机** - 必须在 `std::make_shared` 完成后才能调用 `shared_from_this()` - 在构造函数中调用会导致崩溃(因内部 `weak_ptr` 未初始化)[^3] 3. **线程安全性** - `shared_from_this()` 本身线程安全 - 但纤程状态需额外同步机制 > **设计意义**:通过 `enable_shared_from_this` 实现纤程的自主指针管理,使得纤程能安全地传递自身引用或加入调度队列,同时避免手动内存管理错误[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值