从矢量中删除元素后出现隔离错误

Segfault after deleting elements from vector

本文关键字:隔离 错误 元素 删除      更新时间:2023-10-16

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

鉴于我有一个完整的项目,我将尝试简化问题: 我有一个GameManager类,它特别包含一个Menu对象(和其他东西,但我认为这并不重要。游戏管理器用于初始化此对象并对其进行管理。 菜单包含一个 Button 向量(每个按钮都有一个 lambda,用于在用户单击它时执行操作(。

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

为了说明它是如何工作的,我将举一个例子:如果用户单击"加载"按钮,游戏管理器将删除菜单中的当前按钮,并在该菜单中添加新按钮。

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(( 的最后一行似乎是正确的,并且紧接着变得无效。 如果我不清除按钮的向量(菜单>清除指令(,程序就可以工作,但是,最后一个按钮仍然在这里(。

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

  1. menu维持某些button的生命周期
  2. button维持lambda的使用寿命
  3. 单击"buttonlambda"时会清除menu
  4. menu析构函数清除buttonbutton清除lambda
  5. lambda在实际上已经被销毁时继续执行 ->未定义的行为以崩溃结束

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

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

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

当您销毁"创建"按钮的unique_ptr时,它会破坏"创建"按钮。

我假设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 调用它。然后,如果您在该功能中销毁按钮也没关系。销毁其代码当前正在运行的对象是可以的,只要您小心不要在销毁该对象后访问该对象即可!这对于 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.
}