当捕获的资源可能被销毁时,如何正确创建std::function?
正确处理捕获可能销毁资源的std::function回调
在你给出的场景中,lambda捕获裸指针后,对象销毁时调用std::function会导致悬空指针访问(行为未定义)。以下是几种针对不同场景的解决方案,适配你提到的“无法直接改为std::shared_ptr”的实际情况:
方案1:利用std::weak_ptr + std::enable_shared_from_this(推荐)
如果你的类实例本质上是被std::shared_ptr管理的(即使是传递自身),可以让类继承std::enable_shared_from_this,通过生成弱引用(weak_ptr)来捕获,调用时先验证对象是否存活:
#include <memory> #include <functional> struct Foo : std::enable_shared_from_this<Foo> { int i = 0; void addInt(int input) { i += input; } // 生成绑定自身的回调函数 std::function<void(int)> createCallback() { auto weak_self = weak_from_this(); return [weak_self](int b) { // 尝试将弱引用转换为强引用,验证对象是否存活 if (auto self = weak_self.lock()) { self->addInt(b); } // 对象已销毁时,可选择跳过操作或执行错误处理 }; } }; int main() { auto foo = std::make_shared<Foo>(); std::function<void(int)> lambda = foo->createCallback(); lambda(8); // 正常执行 foo.reset(); // 销毁Foo实例 lambda(8); // 检测到对象已销毁,无操作,避免悬空访问 return 0; }
原理:enable_shared_from_this允许对象获取自身的shared_ptr,进而生成weak_ptr。lambda捕获weak_ptr而非裸指针,调用时通过lock()转换为shared_ptr——如果对象已销毁,lock()返回空,此时不会执行访问对象的逻辑。
方案2:手动添加生命周期标记(无智能指针场景)
如果无法使用智能指针,可以给类添加一个原子布尔标记,跟踪对象的存活状态,lambda调用前先检查标记:
#include <functional> #include <atomic> struct Foo { int i = 0; std::atomic<bool> is_alive{true}; // 标记对象是否存活 ~Foo() { is_alive.store(false, std::memory_order_relaxed); } void addInt(int input) { i += input; } }; int main() { Foo* lostMemory = new Foo(); std::function<void(int)> lambda = [lostMemory](int b) { // 检查对象是否存活 if (lostMemory->is_alive.load(std::memory_order_relaxed)) { lostMemory->addInt(b); } }; lambda(8); // 正常执行 delete lostMemory; lambda(8); // 检测到对象已销毁,不执行操作 return 0; }
注意:如果是多线程场景,要确保原子操作的内存顺序正确性,示例中用std::memory_order_relaxed足够满足标记可见性需求。
方案3:观察者模式主动管理回调生命周期
如果回调是注册到类内部的,可以在类销毁时主动清空所有关联的回调列表,避免外部调用时访问悬空对象:
#include <functional> #include <vector> struct Foo { int i = 0; std::vector<std::function<void(int)>> callbacks; ~Foo() { callbacks.clear(); // 销毁时清空所有回调 } void addInt(int input) { i += input; } void registerCallback(std::function<void(int)> cb) { callbacks.push_back(std::move(cb)); } void triggerCallbacks(int val) { for (auto& cb : callbacks) { cb(val); } } }; int main() { auto foo = std::make_unique<Foo>(); foo->registerCallback([foo = foo.get()](int b) { foo->addInt(b); }); foo->triggerCallbacks(8); // 正常执行 foo.reset(); // 销毁Foo实例,同时清空内部回调 // 此时外部如果持有回调,调用时也不会访问到Foo(如果是内部触发的话) return 0; }
这种方式更适合回调由类自身触发的场景,若回调被外部持有,仍需结合前两种方案的检查逻辑。
内容的提问来源于stack exchange,提问作者Natio2
相关产品推荐
相关产品推荐

