Segfault после удаления элементов из вектора - PullRequest
2 голосов
/ 17 февраля 2020

В настоящее время я работаю над проектом, точнее игрой с танграмом. У меня проблема с segfault, и я не понимаю, почему.

Учитывая, что у меня есть целый проект, я постараюсь упростить проблему: у меня есть класс GameManager, который содержит, в частности, объект Menu ( и другие вещи, но я не думаю, что это важно. GameManager используется для инициализации этого объекта и управления им. Меню содержит вектор Button (каждая кнопка имеет лямбду, которая выполняет действие, когда пользователь нажимает на нее) .

std::vector<std::unique_ptr<Button>> buttons;

Чтобы проиллюстрировать, как это работает, я приведу пример: если пользователь нажимает кнопку «Загрузить», gameManager удаляет текущие кнопки, содержащиеся в меню, и добавляет новые кнопки в этом меню.

void GameManager::initMainMenuButtons() {
...
menu -> addButton(std::unique_ptr<Button>(new Button(x1, y1, x2, y2, "Create",
    [this]{
        std::cout << "Create level" << std::endl;
        menu->clear()
        initCreateLevelButtons();
        actionManager->setMenu(menu);
    }
)));
...
}

В этом примере кода у меня есть метод initMainMenuButtons, который добавляет несколько кнопок в меню, например «Загрузить» или «Выход». Когда пользователь нажимает «Создать», я хочу изменить интерфейс (добавление и удаление кнопок). Итак, чтобы удалить кнопки, я вызываю метод clear ()

void Menu::clear() {
  buttons.clear();
  decorationPieces.clear(); // not interesting
}

Я использую unique_ptr, поэтому я не не нужно удалять кнопки вручную. Пока проблем нет: вектор кнопок кажется пустым (размер равен 0). Затем вызывается метод initCreateLevelButtons (). Этот метод очень похож на initMainMenu: он добавляет кнопки в меню, больше ничего. Во время этого вызова кнопки, кажется, правильно добавлены в вектор, я напечатал содержимое вектора в конце, и вектор содержит правильные кнопки.

И там возникает проблема: после вызова initCreateLevelButtons (), возникает ошибка, когда я хочу использовать меню, поэтому actionManager->setMenu(menu); не работает. Я попытался напечатать меню std::cout << menu << std::endl и проверить, является ли этот указатель nullptr, но он также не работает. Я не понимаю, почему меню кажется правильным в последней строке initCreateLevelButtons () и становится недействительным сразу после. Если я не очищаю вектор кнопок (меню-> инструкция очистки), программа работает, но последние кнопки все еще здесь).

Я попытался использовать необработанные указатели, и я заметил, что Программа может очищать вектор, пока кнопки не удалены (если я добавлю al oop для удаления кнопок, проблема возникнет), поэтому я пришел к выводу, что проблема заключается в удалении кнопок. Я не понимаю, почему, я застрял. Я не знаю, объяснил ли я это хорошо, потому что, как я уже сказал, код является частью целого проекта, трудно вводить классы, не вводя другие вещи. если вам нужны подробности или полный код методов, я могу предоставить их.

Ответы [ 2 ]

5 голосов
/ 17 февраля 2020
  1. menu поддерживает время жизни некоторых button
  2. button время жизни lambda
  3. при нажатии button lambda очищает menu
  4. menu деструктор очищает button, button очищает lambda
  5. lambda продолжает выполнение, когда оно фактически уже уничтожено -> неопределенное поведение заканчивается с cra sh

Теперь вопрос: у вас есть Button класс?
Если да, то самый простой способ исправить это - вызвать копию лямбды в кнопке.

3 голосов
/ 17 февраля 2020

Когда вы звоните menu->clear(), он вызывает buttons.clear().

Когда вы звоните buttons.clear(), он уничтожает все элементы buttons.

Когда вы уничтожаете unique_ptr для кнопки «Создать» она уничтожает кнопку «Создать».

Я предполагаю, что обратный вызов button является std::function. Когда уничтожается button, то std::function.

Когда уничтожается std::function, ваш лямбда-объект обратного вызова ([this]{...}) уничтожается.

this указатель внутри лямбды хранится в лямбда-объекте. Так что теперь память, в которой содержался указатель this, была освобождена.

Поскольку actionManager является переменной-членом GameManager, actionManager->setMenu(menu) действительно this->actionManager->setMenu(menu), которая вылетает, потому что она использует висячий указатель .

Один из обходных путей - поместить код кнопки в функцию GameManager (так как GameManager не уничтожается) и вызвать ее из лямбды. Тогда это нормально, если вы уничтожите кнопку, находясь внутри этой функции. Можно уничтожить объект, код которого в данный момент выполняется , если вы будете осторожны, чтобы не получить доступ к объекту после его уничтожения! Это также нормально с std :: function . Т.е.:

    [this]{
        // move the rest of the code to the CreateLevel function
        this->CreateLevel();

        // At this point the lambda has been destroyed, but it's not a problem
        // because we don't do anything.
    }
...