按值将 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();
        ;
    

这工作正常,当点击按钮时模式关闭,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 而不会出现内存泄漏?

编辑: 所以我通过在 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 会泄漏内存的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中通过引用与按值将向量传递给函数

按值传递列表以发挥作用

C++:编译器能否优化按值传递?

作为参数的函数是不是必须按值传递? [复制]

如何按值将组作为 1 个带逗号的框?

为啥不允许将数组按值传递给 C 和 C++ 中的函数?