2

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

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

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);不起作用。我尝试打印 menu std::cout << menu << std::endl,并测试此指针是否为 nullptr,但它也不起作用。我不明白为什么菜单在 initCreateLevelButtons() 的最后一行似乎是正确的,然后就变得无效。如果我不清除按钮的向量(菜单->清除指令),程序可以工作,但是最后一个按钮仍然在这里)。

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

4

2 回答 2

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

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

于 2020-02-17T16:58:09.600 回答
3

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

当你调用buttons.clear()它时,它会破坏buttons.

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

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

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

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

因为actionManageris 的成员变量GameManageractionManager->setMenu(menu)所以真的this->actionManager->setMenu(menu)会崩溃,因为它使用了一个悬空指针。

一种解决方法是将按钮代码放在函数中GameManager(因为GameManager未销毁),然后从 lambda 调用它。然后,如果您在该函数内销毁按钮,就可以了。销毁代码当前正在运行的对象是可以的,只要您小心不要在销毁后访问该对象!这也适用于std::function。IE:

    [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.
    }
于 2020-02-17T17:07:10.807 回答