我有以下代码:
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
而不是那些内存泄漏?
修改
所以我通过将mutable
添加到lambda来消除编译错误。
if(modal)
{
modal->getElementById("closeButton")->onClicked = [modal]() mutable {
modal->hide();
modal.reset();
};
}
现在,如果我单击关闭按钮,并关闭应用程序,则没有内存泄漏,因为重置会清除该引用。但如果从未点击按钮,我仍然会收到泄漏。
答案 0 :(得分:14)
您已创建了shared_ptr周期。
在其引用计数达到0之前,不能销毁模态。然后将shared_ptr的副本传递给labal到labmda函数,增加其引用计数。然后,将该lambda函数分配给modal的成员。
这意味着模态总是由其回调函数引用。但是,在modal没有refcount之前,它的回调函数不能被销毁。 Modal最终会因为参考计数为1而陷入困境。
通常的解决方案是将裸指针或(最好)弱指针传递给lambda
答案 1 :(得分:1)
没有
作为这个问题的解决方案,我有以下简单的测试:
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是仿函数对象,当持有者对象(示例中的模态)不再存在或字段被清除(或更新)时,lambda被正确销毁。