删除指针向量时遇到问题

4
我有一个管理器类,持有指向虚基类的指针向量,以允许存储多种子类。在此管理器类的析构函数中,我希望它能循环遍历所有持有的指针并将其删除。然而,我尝试了许多方法,但程序在执行过程中仍然崩溃。
下面是我目前的代码:
for (std::vector<GameState*>::iterator it = gamestates_.begin(); it != gamestates_.end(); ++it){
    delete *it;
    it = gamestates_.erase(it);
}

我还没有尝试过使用unique_ptr,但我相信应该能够处理它们而不使用它们。如果我错了,请纠正我。

编辑:我知道应该在循环后清除向量,但这是在尝试了每种常规方法删除指针后得出的结果。它似乎不喜欢delete命令。

6个回答

6

从向量中删除元素会使迭代器失效,因此您无法在此之后继续迭代。在这种情况下,我不会在循环内删除元素; 相反,我会在循环完成后清除该向量:

for (auto it = gamestates_.begin(); it != gamestates_.end(); ++it){
    delete *it;
}
gamestates_.clear();

尽管如此,如果这是在析构函数中,而且向量即将被销毁,那么清除它也没有意义。
如果您确实需要在循环内部进行删除(例如,因为您只想删除某些元素),那么需要更加小心以保持迭代器的有效性。
for (auto it = gamestates_.begin(); it != gamestates_.end();){ // No ++ here
    if (should_erase(it)) {
        it = gamestates_.erase(it);
    } else {
        ++it;
    }
}

我还没有尝试过使用unique_ptr,但我相信它应该可以处理它而不使用它们。如果我错了,请纠正我。
如果您确实想通过此方式管理动态对象,则请确保遵循“三个规则”:需要实现(或删除)复制构造函数和复制赋值运算符以防止“浅”复制,从而使您拥有两个试图删除相同对象的向量。您还需要注意在任何其他删除或替换它们的地方删除对象。存储智能指针(如果您不需要用于多态性的指针,则为对象本身)将为您处理所有这些内容,因此我始终建议您这样做。
我知道我应该在循环后清除向量,但这是我经过尝试删除指针的每种常规方法后得出的结论。它似乎不喜欢删除命令。
最有可能的原因是您没有遵循“三个规则”,并且在复制向量后意外尝试两次删除相同的对象。还可能是GameState是一个基类,您忘记给它一个虚析构函数,或者指针已被某些其他代码破坏。

这似乎也不起作用。我之前尝试过类似的东西,但似乎没有任何作用。好像它不喜欢删除命令。愚蠢的问题:我假设我不需要包含某个头文件来删除指针。 - James Richmond
Gamestate 包含 init、render、update 和 close 的虚拟调用,但每个子类也会包含一些自己的函数和变量。由于我只想在同一时间运行一个状态,所以当状态处于活动状态时,管理器会调用 init,并在完成后调用 close。析构函数基本上是空的,并且在我引用的那行代码的正上方有一行关闭活动状态的代码。我预计这些类会非常大,因此我试图通过将它们存储在堆上来节省空间。如果它们基本上是空的,它们是否仍需要在基类中具有虚析构函数? - James Richmond
@RustyC:是的,你总是需要一个虚析构函数来通过基类指针删除。确保GameState有一个,并且还要确保管理器类没有意外复制向量。 - Mike Seymour
向量在头文件中被定义(对于该类是私有的),并使用push_back进行添加,我无法看出它在任何时候会被复制。此外,虚析构函数需要特别注意什么吗? - James Richmond
@RustyC:如果您意外复制了整个管理器类,它可能会被复制;您应确保该类遵循三法则中的一项,即不可复制或正确可复制。使用 unique_ptr 将自动为您防止复制。如果基类没有要清理的内容,则虚拟析构函数可以为空;它只需要存在即可。 - Mike Seymour
显示剩余3条评论

2

您的迭代器在每个循环中更新两次:

it = gamestates_.erase(it);

并且
it++

你只需要第一个-它已经指向了容器中的“下一个对象”。

+1 看起来有点不公平,因为你的回答是第一个指出这一点的。 - Daniel Earwicker

0
问题在于你将 it 增加了两次。首先,在调用 it = .erase(it) 时,它返回下一个元素,然后在循环中使用 ++i。你可能会无意中跳过结尾,导致出现问题,更不用说你只会删除向量的每个第二个元素。
一个简单的解决方法是在循环中不改变 it(没有 ++it)。
更好的方法是从向量的末尾实际删除数组,因为从向量内部删除元素会引入所有后续元素的昂贵移动。你当前的算法将以 N^2 的时间运行。尝试像这样做:
while (!gamestates_.empty()) {
    delete gamestates_.back();
    gamestates_.erase(gamestates_.end()-1);
}

您也可以迭代向量的所有元素,然后将其清除:

for (std::vector<GameState*>::iterator it = gamestates_.begin(); it != gamestates_.end(); ++it){
    delete *it;
}
gamestates_.clear();

还要注意,向量的clear()操作也在其析构函数中完成。如果删除过程是某个销毁过程的一部分,其中gamestates_最终被销毁-则根本不需要调用clear()

0

从向量中删除元素会使迭代器失效。通过元素指针删除对象,然后clear()向量的内容。


0

在你的 for 循环头中摆脱 ++it

erase 已经为您提前了。

或者,迭代、删除,然后在迭代之后使用 .clear()


0

最好使用unique_ptr。你说你“应该能够处理它们而不使用它们”,好像让智能指针为你工作是一种可怕的强制。

它们存在是为了让你的生活更轻松,你不必因为没有手动完成艰苦的工作而感到内疚。

而且对于你现有的代码,只需不调用erase即可。向量将被销毁,对吧?它会自己处理所有这些问题。


而且,对于您现有的代码,只需不调用erase函数即可。向量本来就会被销毁,对吧?它会自己处理所有这些问题。除非它没有被销毁,否则它不会出现问题! - Daniel Earwicker
问题说这是在管理类的析构函数中,并且我假设该向量是一个成员子对象。 - Useless

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接