按值将 shared_ptr 传递给 lambda 会泄漏内存
Posted
技术标签:
【中文标题】按值将 shared_ptr 传递给 lambda 会泄漏内存【英文标题】:Passing shared_ptr to lambda by value leaks memory 【发布时间】:2013-09-15 22:47:52 【问题描述】:我有以下代码:
void MyClass::onOpenModalBtnClicked()
uiManager->load(L"data/ui/testmodal.json");
std::shared_ptr<UIElement> modal = uiManager->getElementById("loginModal");
if(modal)
modal->getElementById("closeButton")->onClicked = [modal]()
modal->hide();
;
这工作正常,当点击按钮时模式关闭,onClicked
是std::function
。
我的应用程序开头也有这个:
#if defined(DEBUG) | defined (_DEBUG)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
这会在应用终止时打印出内存泄漏。
使用上面的代码,我会遇到很多内存泄漏,如果我将代码更改为下面的代码,它们都会消失:
void MyClass::onOpenModalBtnClicked()
uiManager->load(L"data/ui/testmodal.json");
std::shared_ptr<UIElement> modal = uiManager->getElementById("loginModal");
if(modal)
modal->getElementById("closeButton")->onClicked = [this]()
uiManager->getElementById("loginModal")->hide();
;
我假设通过值传入shared_ptr
会使引用计数增加1,然后这个引用永远不会超出范围,或者在报告内存泄漏后它会超出范围。所以我在使用 shared_ptr 后尝试在 lambda 中调用 reset 但随后出现此编译器错误:
Error 1 error C2662: 'void std::shared_ptr<_Ty>::reset(void) throw()' : cannot convert 'this' pointer from 'const std::shared_ptr<_Ty>' to 'std::shared_ptr<_Ty> &'
所以问题是如何使用捕获的modal
而不会出现内存泄漏?
编辑:
所以我通过在 lambda 中添加 mutable
来消除编译错误。
if(modal)
modal->getElementById("closeButton")->onClicked = [modal]() mutable
modal->hide();
modal.reset();
;
现在,如果我单击关闭按钮并关闭应用程序,则不会出现内存泄漏,因为重置会清除该引用。但如果按钮从未被点击,我仍然会得到泄漏。
【问题讨论】:
您确定持有 onClicked 的对象被正确销毁了吗?在 onClicked 被销毁之前,无法销毁shared_ptr
。
如果我捕获modal
,则永远不会调用关闭按钮的析构函数,实际上没有任何析构函数被调用为模态的子项。这让我相信捕获的modal
永远不会超出范围。
谢谢,那是我没看到的。接听来电。
【参考方案1】:
您已经创建了一个 shared_ptr 循环。
modal 在其引用计数达到 0 之前不能被销毁。然后将 shared_ptr 的副本传递给 modal 到 labmda 函数,增加其引用计数。然后将该 lambda 函数分配给 modal 的成员。
这意味着模式总是由它的回调函数引用。但是,直到 modal 没有引用计数,它的回调函数才能被销毁。 Modal 最终被 1 的引用计数卡住了。
通常的解决方案是将裸指针或(最好)弱指针传递给 lambda
【讨论】:
【参考方案2】:没有。
作为这个问题的解决方案,我有以下简单的测试:
class Modal
public:
Modal() onClick = nullptr;
std::function<void()> onClick;
;
class Data
public:
string* message;
Data() message = nullptr;
Data(string s) message = new string(s); LOG << "CREATED" << NL;
Data(Data&& d) LOG << "MOVE CTR" << NL; message = d.message; d.message = nullptr;
Data(const Data& d) LOG << "COPY CTR" << NL; message = new string(*d.message);
virtual ~Data() if (message) delete message; LOG << "DESTROYED" << NL;
;
Modal modal;
std::shared_ptr<Data> p = std::make_shared<Data>(Data("Will it be deleted?"));
LOG << *(p->message) << " " << p.use_count() << NL;
modal.onClick = [p]()
LOG << *(p->message) << " " << p.use_count() << NL;
;
modal.onClick();
modal.onClick();
modal.onClick = nullptr;
LOG << "End of test" << NL;
我得到以下图片作为输出:
正如您所看到的,当您覆盖 onClick 处理程序时,会调用destroy 事件。所以在 lambda 体内不需要任何 reset() 调用。请参见参考计数器输出。 lambda 是仿函数对象,当持有者对象(例如模态)不再存在或字段被清除(或更新)时,它会被正确销毁。
【讨论】:
以上是关于按值将 shared_ptr 传递给 lambda 会泄漏内存的主要内容,如果未能解决你的问题,请参考以下文章