从向量中删除元素后出现段错误

Segfault after deleting elements from vector

我目前正在做一个项目,更准确地说是一个七巧板游戏。 我有一个段错误问题,我不明白为什么。

鉴于我有一个完整的项目,我会尽量简化问题: 我有一个 GameManager class,它特别包含一个 Menu 对象(和其他东西,但我认为这并不重要。gameManager 用于初始化这个对象并管理它。 Menu 包含一个 Button 向量(每个按钮都有一个 lambda,当用户点击它时执行一个动作)。

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

为了说明它是如何工作的,我将举一个例子:如果用户点击 "Load" 按钮,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,它在菜单中添加了几个按钮,例如 "Load" 或 "Quit"。 当用户点击 "Create" 时,我想更改界面(添加和删除按钮)。所以,为了删除按钮,我调用方法 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() 的最后一行似乎是正确的,但之后就变得无效了。 如果我不清除按钮向量(菜单->清除指令),程序可以运行,但是,最后一个按钮仍然在这里。

我尝试使用原始指针,我注意到只要不删除按钮,程序就可以清除向量(如果我添加一个循环来删除按钮,就会出现问题),所以,我得出的结论是问题是删除按钮。我不明白为什么,我被卡住了。 我不知道我是否解释得很好,因为正如我已经说过的,代码是整个项目的一部分,很难引入 classes 而不引入其他东西。 如果你想要细节或者方法的完整代码,我可以提供。

  1. menu 维持一些 button
  2. 的寿命
  3. button 维持寿命 lambda
  4. 当您点击 button lambda 清除 menu
  5. menu析构函数清除buttonbutton清除lambda
  6. lambda 在实际上已经被销毁时继续执行 -> 未定义的行为以崩溃结束

现在的问题是:你拥有Buttonclass吗?
如果是,那么修复它的最简单方法是在按钮中调用 lambda 的副本。

当您调用 menu->clear() 时,它会调用 buttons.clear()

当您调用 buttons.clear() 时,它会破坏 buttons 的所有元素。

当您销毁 "Create" 按钮的 unique_ptr 时,它会销毁 "Create" 按钮。

我假设 button 的回调是 std::function。当 button 被销毁时, std::function.

也会被销毁

std::function 被销毁时,您的回调 lambda 对象 ([this]{...}) 也被销毁。

lambda 内部的 this 指针存储在 lambda 对象中。所以现在保存 this 指针的内存已被释放。

由于actionManagerGameManager的成员变量,actionManager->setMenu(menu)实际上是this->actionManager->setMenu(menu),因为它使用了悬空指针而崩溃。

一种解决方法是将按钮代码放在 GameManager 的函数中(因为 GameManager 未被破坏),并从 lambda 调用它。然后,如果您在该函数内销毁按钮也没关系。 It's okay to destroy an object whose code is currently running, as long as you are careful to not access the object after it's destroyed! This is also okay with 。即:

    [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.
    }