16

我有以下代码:

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_ptrby 值会将引用计数增加 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();
    };
}

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

4

2 回答 2

19

您已经创建了一个 shared_ptr 循环。

modal 在其引用计数达到 0 之前不能被销毁。然后将 shared_ptr 的副本传递给 modal 到 labmda 函数,增加其引用计数。然后将该 lambda 函数分配给 modal 的成员。

这意味着模态总是由它的回调函数引用。但是,直到 modal 没有引用计数,它的回调函数才能被销毁。Modal 最终陷入引用计数为 1 的问题。

通常的解决方案是将裸指针或(最好)弱指针传递给 lambda

于 2013-09-15T23:10:40.613 回答
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 是仿函数对象,当持有者对象(例如模态)不再存在或字段被清除(或更新)时,它会被正确销毁。

于 2014-10-22T17:18:34.437 回答