Передача shared_ptr в lambda по памяти значений утечек

У меня есть следующий код:

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, но затем я получил эту ошибку компилятора:

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 к лямбде.

if(modal) {
    modal->getElementById("closeButton")->onClicked = [modal]() mutable {
        modal->hide();
        modal.reset();
    };
}

Теперь, если я нажму кнопку закрытия и закрою приложение, утечек памяти не будет, так как сброс очищает эту ссылку. Но если кнопка никогда не нажимается, я все равно получаю утечки.

Ответ 1

Вы создали цикл shared_ptr.

modal не может быть уничтожен до тех пор, пока его счетчик ссылок не достигнет 0. Затем вы передаете копию shared_ptr в modal в функцию labmda, увеличивая счетчик ссылок. Затем вы назначаете эту лямбда-функцию в член модального.

Это означает, что модально всегда ссылается на свою функцию обратного вызова. Однако его функция обратного вызова не может быть уничтожена до тех пор, пока модальное значение не будет возвращено. Модаль заканчивается тем, что застрял с номером ref.

Обычное решение состоит в том, чтобы передать либо голый указатель, либо (предпочтительно) слабый указатель на лямбда

Ответ 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;
}

Где я получаю следующее изображение в качестве вывода:

Test output

Как вы можете видеть, когда вы перезаписываете обработчик обработчика onClick, вызывается событие. Таким образом, нет необходимости в каких-либо вызовах reset() внутри тела лямбды. См. Вывод счетчика. Лямбда является объектом-функтором и надлежащим образом уничтожается, когда объект-держатель (модальный в примере) больше не существует или поле очищается (или обновляется).