【问题标题】:Having trouble deleting vector of pointers删除指针向量时遇到问题
【发布时间】:2013-09-02 13:47:44
【问题描述】:

我有一个管理器类,其中包含一个指向虚拟基类的指针向量,以允许将各种子类存储在那里。在这个管理器类的析构函数中,我希望它循环遍历它持有的所有指针并删除它们。但是,我尝试了许多我遇到的方法,程序在执行过程中不断崩溃。

我的当前代码如下所示:-

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

我还没有尝试过的一件事是使用 unique_ptr 但我确信这应该能够在不使用它们的情况下处理它。如果我错了,请纠正我。

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

【问题讨论】:

    标签: c++ pointers vector delete-operator


    【解决方案1】:

    从向量中删除一个元素会使迭代器失效,因此你不能在之后继续迭代。在这种情况下,我不会删除循环中的元素;之后我会清除向量:

    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,但我确信这应该能够在不使用它们的情况下处理它。如果我错了,请纠正我。

    如果您确实想通过这种方式管理动态对象,请确保遵循Rule of Three:您需要实现(或删除)复制构造函数和复制赋值运算符以防止“浅”复制留下两个试图删除相同对象的向量。您还需要注意删除任何其他删除或替换对象的位置。存储智能指针(或对象本身,如果您不需要指针来实现多态性)将为您处理所有这些事情,所以我总是建议这样做。

    我知道我应该在循环之后清除向量,但这是我在尝试了所有删除指针的正常方法后得出的结果。它似乎不喜欢删除命令。

    最可能的原因是您没有遵循三法则,并且在复制向量后不小心尝试删除相同的对象两次。也有可能GameState 是一个基类,而您忘记给它一个虚拟析构函数,或者指针已被其他代码破坏。

    【讨论】:

    • 这似乎也不起作用。我之前确实尝试过这样的事情,但似乎没有任何效果。就好像它不喜欢删除命令一样。愚蠢的问题:我假设我不需要包含某个标题来删除指针。
    • @RustyC:delete 命令很好,如果它是一个有效的指针。您是否不小心复制了向量并删除了两次对象? GameState 是基类,实际对象有不同的类型吗?如果是这样,请确保它具有虚拟析构函数。如果不是,你为什么要存储指针?
    • Gamestate 拥有对 init、render、update 和 close 的虚拟调用,但它的每个子级也将拥有许多自己的函数和变量。因为我只希望一次运行一个,所以当状态处于活动状态时管理器调用 init 并在状态完成时调用关闭。析构函数本质上是空的,并且在我引用的行的正上方有一行可以关闭活动状态。我希望这些类非常大,所以我试图通过将它们存储在堆上来节省空间。如果它们本质上是空的,它们是否仍然需要在基类中使用虚拟析构函数?
    • @RustyC:是的,你总是需要一个虚拟析构函数来通过基类指针删除。确保 GameState 有一个,并确保管理器类不会意外复制向量。
    • 该向量在标题中定义(类私有)并使用 push_back 添加,我看不到它在任何时候将如何被复制。另外,虚拟析构函数需要什么特别的东西吗?
    【解决方案2】:

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

    it = gamestates_.erase(it);
    

    it++
    

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

    【讨论】:

    • +1 鉴于您是第一个指出这一点的答案,您没有投票似乎有点不公平。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      去掉for循环头中的++it

      erase 已经为你提前了。

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

      【讨论】:

        【解决方案5】:

        更喜欢使用unique_ptr。你说你应该能够在不使用它们的情况下处理它,就好像让一个智能指针为你做这项工作是某种可怕的强加。

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

        使用您现有的代码,不要调用erase。无论如何,向量都会被破坏,对吧?它会自己处理所有这些。

        【讨论】:

        • “用你现有的代码,不要调用erase。无论如何,向量都会被销毁,对吧?它会自己处理所有这些。”除非不是,否则不会!
        • 问题说这是在管理器类的析构函数中,我假设向量是成员子对象。
        【解决方案6】:

        问题是您将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()

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-04-14
          • 2013-09-24
          • 2012-08-12
          • 2015-08-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多