boost::enable_shared_from_this

本文介绍如何利用boost::enable_shared_from_this确保在异步操作的回调中,发起操作的对象依然有效。通过示例说明在没有使用此特性时可能引发的问题及解决办法。

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

boost::enable_shared_from_this
这个类能够让一个被shared_ptr管理生命周期的类能够在自己的成员函数内部使用自己的shared_ptr

在什么场景下需要使用一个shared_ptr呢?直接使用this指针不行吗?
想象一下这样的场景:在类中发起一个异步操作,回调函数callback在被调用时要保证发起操作的对象仍然存在。那么使用this是不合适的,因为它有可能已经析构了。而使用shared_ptr则能够保证对象的引用计数至少为1,这也就保证了对象仍然有效。

举个例子:

在不使用boost::enable_shared_from_this时,在自己的成员函数获取自身的shared_ptr成了问题,如下例所示:

#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

using namespace std;

class Foo {
public:
    Foo() : n_(8) { cout << "ctor" << endl; }
    ~Foo(){ cout << "dtor" << endl; }
    void DoSomething()
    {
        boost::shared_ptr<Foo> sp(this);
        cout << n_ << endl;
    }
private:
    int n_;
};

int main()
{
    boost::shared_ptr<Foo> f(new Foo);
    f->DoSomething();
    return 0;
}

这种用法是错误的,一个对象被两个智能指针管理,很明显,析构函数会被调用两次:

ctor
8
dtor
dtor

这时需要使用boost::enable_shared_from_this

class Foo2 : public boost::enable_shared_from_this<Foo2> {
public:
    Foo2() : n_(8) { cout << "ctor" << endl; }
    ~Foo2(){ cout << "dtor" << endl; }
    void DoSomething()
    {
        boost::shared_ptr<Foo2> sp = shared_from_this();
        cout << n_ << endl;
    }

    boost::shared_ptr<Foo2> GetPointer()
    {
        return shared_from_this();
    }

private:
    int n_;
};

int main()
{
    boost::shared_ptr<Foo2> f(new Foo2);
    f->DoSomething();

    cout << "use count:" << f.use_count() << endl;
    boost::shared_ptr<Foo2> t = f->GetPointer();
    cout << "use count:" <<f.use_count() << endl;

    return 0;
}

输出:

ctor
8
use count:1
use count:2
dtor
<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_ptr在shared_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、付费专栏及课程。

余额充值