C++ 如何避免成员回调的循环引用&;共享ptr?
在我们的代码库中,有过多的情况如下:C++ 如何避免成员回调的循环引用&;共享ptr?,c++,c++11,callback,shared-ptr,C++,C++11,Callback,Shared Ptr,在我们的代码库中,有过多的情况如下: auto f = std::make_shared<Foo>(); auto b = std::make_shared<Bar>(); //void Foo::DoStuff( std::shared_ptr<Foo>, int ) auto func = std::bind( &Foo::DoStuff, f, std::_1 ); b->RegisterFunc( func ); auto f=std
auto f = std::make_shared<Foo>();
auto b = std::make_shared<Bar>();
//void Foo::DoStuff( std::shared_ptr<Foo>, int )
auto func = std::bind( &Foo::DoStuff, f, std::_1 );
b->RegisterFunc( func );
auto f=std::make_shared();
自动b=std::使_共享();
//void Foo::DoStuff(std::shared_ptr,int)
auto-func=std::bind(&Foo::DoStuff,f,std:_1);
b->RegisterFunc(函数);
显然,将shared_ptr
放入存储的回调中通常是个坏主意,因为它经常导致f
永远不会被破坏。通常,您只需传入一个弱\u ptr
,然后检查它在执行时是否仍然有效
不幸的是,DoStuff()
修改Foo
对象的内部状态,并且是一个成员函数,因此它要求指针有效以避免崩溃。这会导致循环引用,需要手动干预,如调用Foo::Shutdown()
或其他方法来删除它们
有没有办法绕过存储成员函数回调的循环引用
注:就问题而言,我要求绑定函数为非静态成员函数。它在我们的代码中太普遍了,无法证明完全重写每个示例是正确的,即使它显然是设计疏忽。正如您所说,您必须使用
弱\u ptr
并检查它是否有效<代码>弱\u ptr可用于创建指向对象的有效指针,因此您仍然可以对从弱\u ptr
获取的指针调用DoStuff()
。我不清楚你为什么认为弱\u ptr
不起作用
这是您的示例,但现在它显示了一个潜在的循环引用,使用weak\u ptr
#include <memory>
#include <functional>
#include <vector>
#include <cassert>
struct Foo {
int x = 0;
using callback_t = std::function<void()>;
std::vector<callback_t> callbacks;
void DoStuff() { x = 1; } //mutate some state
void RegisterFunc(callback_t callback) { callbacks.push_back(std::move(callback)); }
};
using Bar = Foo;
template <class T>
auto wrap_shared_ptr_callback(const std::shared_ptr<T>& p, void(T::*f)()) {
return [p = std::weak_ptr{ p }, f]() {
auto ptr = p.lock();
if (ptr) (*ptr.*f)();
};
}
void create_circular_reference(std::shared_ptr<Foo> f, std::shared_ptr<Bar> b) {
auto f_callback = wrap_shared_ptr_callback(f, &Foo::DoStuff);
b->RegisterFunc( f_callback );
auto b_callback = wrap_shared_ptr_callback(b, &Bar::DoStuff);
f->RegisterFunc( b_callback );
// test callbacks
b->callbacks.front()();
f->callbacks.front()();
}
int main() {
auto f = std::make_shared<Foo>();
auto b = std::make_shared<Bar>();
// keep weak ptrs so we can check they've been destroyed correctly
auto f_w = std::weak_ptr{ f };
auto b_w = std::weak_ptr{ b };
create_circular_reference(std::move(f), std::move(b));
assert(f_w.expired());
assert(b_w.expired());
}
#包括
#包括
#包括
#包括
结构Foo{
int x=0;
使用callback\u t=std::函数;
std::向量回调;
void DoStuff(){x=1;}//更改某些状态
void RegisterFunc(callback_t callback){callbacks.push_back(std::move(callback));}
};
使用Bar=Foo;
样板
自动换行共享ptr回调(const std::shared ptr&p,void(T::*f)(){
返回[p=std::弱_ptr{p},f](){
自动ptr=p.锁();
if(ptr)(*ptr.*f)();
};
}
无效创建循环引用(标准::共享ptr f,标准::共享ptr b){
自动f_callback=wrap_shared_ptr_callback(f,&Foo::DoStuff);
b->RegisterFunc(f_回调);
自动b_callback=wrap_shared_ptr_callback(b,&Bar::DoStuff);
f->RegisterFunc(b_回调);
//测试回调
b->callbacks.front();
f->callbacks.front();
}
int main(){
自动f=std::使_共享();
自动b=std::使_共享();
//保持弱PTR,以便我们检查它们是否已正确销毁
自动f_w=std::弱_ptr{f};
自动b_w=std::弱b_ptr{b};
创建循环参考(标准::移动(f),标准::移动(b));
断言(f_w.expired());
断言(b_w.expired());
}
对于上下文,代码在我们拥有的端到端系统上运行。大多数进程实际上只是在通电/断电时分配/销毁;这隐藏了问题(大部分)。如果没有,我们采用显式调用Foo::Shutdown方法。如果您通过lambda定义func,该lambda通过值捕获共享的\u ptr,它将在您销毁func时销毁,而不是之前销毁。循环引用在哪里func
指的是f
,b
指的是func
。。没有循环。在本例中,我猜您的意思是在从a
到b
的回调中包含一个引用,这将创建一个循环引用。如果是这样,您可能需要编辑您的问题以反映这一点