【问题标题】:Segfault after deleting elements from vector从向量中删除元素后的段错误
【发布时间】:2023-03-29 18:51:01
【问题描述】:

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

鉴于我有一个完整的项目,我将尝试简化问题: 我有一个 GameManager 类,其中特别包含一个 Menu 对象(和其他东西,但我认为这并不重要。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-&gt;setMenu(menu); 不起作用。我尝试打印菜单std::cout &lt;&lt; menu &lt;&lt; std::endl,并测试该指针是否为nullptr,但它也不起作用。我不明白为什么菜单在 initCreateLevelButtons() 的最后一行似乎是正确的,然后就变得无效。 如果我不清除按钮的矢量(菜单->清除指令),程序可以工作,但是最后一个按钮仍然在这里)。

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

【问题讨论】:

  • 欢迎来到 SO,@Eradan。请尝试缩短您的问题并提供重现您问题的minimal example。它不仅可以帮助您调试问题,还可以帮助我们帮助您。
  • menu-&gt;clear() 将导致*this(“创建”按钮)被销毁,this 成为一个悬空指针。它后面的两个调用显然是通过this 访问成员变量。因此,通过访问生命周期已结束的对象,您的程序会表现出未定义的行为。
  • 旁注:在迭代容器时从容器中移除项目需要小心。错误地删除一个项目并且您破坏了迭代器(并且无法安全地继续)或更改计数(注意不要超出容器的新端)。查找the Erase-Remove Idiom
  • 感谢您的回答,给您一个最小的示例而不给您整个项目有点复杂^^。我将尝试以更简单的方式重现该问题。

标签: c++ vector segmentation-fault smart-pointers raw-pointer


【解决方案1】:
  1. menu 维持一些 button 的生命周期
  2. button维持lambda的寿命
  3. 当您单击button lambda 清除menu
  4. menu 析构函数清除 button, button 清除 lambda
  5. lambda 在实际上已经被销毁时继续执行 -> 未定义的行为以崩溃结束

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

【讨论】:

  • 有效!谢谢!我一直在寻找问题的原因。我明白了问题并解决了,其实很简单,怪我自己挡住了这个问题^^,再次感谢。
【解决方案2】:

当您调用menu-&gt;clear() 时,它会调用buttons.clear()

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

当您销毁“创建”按钮的unique_ptr 时,它会销毁“创建”按钮。

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

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

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

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

一种解决方法是将按钮代码放在GameManager 的函数中(因为GameManager 没有被破坏),然后从lambda 调用它。然后,如果您在该函数内销毁按钮,就可以了。 It's okay to destroy an object whose code is currently running,只要你小心不要在对象被销毁后访问它! 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.
    }

【讨论】:

  • 感谢您的解决方案 :) 我没有想到 lambda 被破坏的事实,但这很明显。我认为问题更复杂
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-05
  • 2020-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多