通过值将shared_ptr传递给lambda会泄漏内存

时间:2013-09-15 22:47:52

标签: c++ lambda shared-ptr

我有以下代码:

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();
        };
    }
}

此工作正常,单击按钮时,模式将关闭,onClickedstd::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();
    };
}

现在,如果我单击关闭按钮,并关闭应用程序,则没有内存泄漏,因为重置会清除该引用。但如果从未点击按钮,我仍然会收到泄漏。

2 个答案:

答案 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;
}

我得到以下图片作为输出:

Test output

正如您所看到的,当您覆盖onClick处理程序时,会调用destroy事件。因此不需要在lambda体内进行任何reset()调用。参见参考计数器输出。 lambda是仿函数对象,当持有者对象(示例中的模态)不再存在或字段被清除(或更新)时,lambda被正确销毁。