【问题标题】:How to erase & delete pointers to objects stored in a vector?如何擦除和删除指向存储在向量中的对象的指针?
【发布时间】:2019-12-02 08:00:00
【问题描述】:

我有一个向量,它存储指向许多动态实例化的对象的指针,我正在尝试遍历向量并删除某些元素(从向量中删除并销毁对象),但我遇到了麻烦。这是它的样子:

    vector<Entity*> Entities;
    /* Fill vector here */
    vector<Entity*>::iterator it;
    for(it=Entities.begin(); it!=Entities.end(); it++)
        if((*it)->getXPos() > 1.5f)
            Entities.erase(it);

当任何实体对象达到 xPos>1.5 时,程序会因断言错误而崩溃... 有谁知道我做错了什么?

我正在使用 VC++ 2008。

【问题讨论】:

  • 请用您使用的语言/环境标记您的问题,以便我们从主页知道您在使用什么(您将获得更多视图)。
  • 但你不知道他在其余代码中对向量做了什么!一般来说,向量应该是首选容器,其他条件相同。
  • 一般来说,你应该更喜欢 STL 算法而不是手写循环。
  • 我同意这种特殊情况需要一个列表,但是在没有先验知识知道这个循环还需要做什么的情况下,现在很难推荐移动到一个列表。我建议的 STL 算法代码是 O(n)。显式循环 + 列表也是 O(n),但是您放弃了引用的局部性和随机访问,这很重要,我建议尽可能避免这种情况。我不同意算法不太清楚。它由一个简单的仿函数和三个步骤组成。如果一个程序员知道如何使用,而且真的,每个c++程序员都应该知道,那么它很容易阅读。
  • 为什么他妈的没有简单的“删除(元素)”方法,而是在我们必须放置这个愚蠢的 begin() 和 end() 调用的任何地方?

标签: c++ visual-c++ vector iterator erase


【解决方案1】:

一旦你修改了向量,所有未完成的迭代器都会失效。换句话说,您不能在迭代时修改向量。想想这对记忆有什么影响,你就会明白为什么。我怀疑您的断言是“无效迭代器”断言。

std::vector::erase() 返回一个迭代器,您应该使用它来替换您正在使用的迭代器。见here

【讨论】:

  • erase 实际上只是使指向被擦除项和之后元素的迭代器失效,指向较低元素的迭代器不会失效。
【解决方案2】:
if((*it)->getXPos() > 1.5f)
{
   delete *it;
   it = Entities.erase(it);
}

【讨论】:

  • 这是不正确的,因为从 erase() 返回的迭代器在擦除后会递增。
  • 我在这里没有看到任何循环
【解决方案3】:

您需要小心,因为erase() 会使现有的迭代器失效。但是,它将返回一个您可以使用的新的有效迭代器:

for ( it = Entities.begin(); it != Entities.end(); ) {
   if( (*it)->getXPos() > 1.5f ) {
      delete * it;  
      it = Entities.erase(it);
   }
   else {
      ++it;
   }
}

【讨论】:

  • 似乎工作,虽然我想知道这和 Keand64 的答案有什么区别? vector::erase() 声称调用对象的析构函数,“delete * it;”也是如此。有必要吗?
  • 指针没有析构函数。只有当它是实体值的集合时,才会调用向量中事物的析构函数。因此,如果您希望避免内存泄漏,调用 delete 是必不可少的。
  • @Gman 我认为您的意思是引用迭代器,而不是指针。
  • 为什么不按索引向后迭代?
【解决方案4】:

做到这一点的“正确”方法是使用算法:

#include <algorithm>
#include <functional>

// this is a function object to delete a pointer matching our criteria.
struct entity_deleter
{
    void operator()(Entity*& e) // important to take pointer by reference!
    { 
        if (e->GetXPos() > 1.5f)
        {
            delete e;
            e = NULL;
        }
}

// now, apply entity_deleter to each element, remove the elements that were deleted,
// and erase them from the vector
for_each(Entities.begin(), Entities.end(), entity_deleter());
vector<Entity*>::iterator new_end = remove(Entities.begin(), Entities.end(), static_cast<Entity*>(NULL));
Entities.erase(new_end, Entities.end());

现在我知道你在想什么了。您认为其他一些答案更短。 但是,(1)这种方法通常编译成更快的代码——尝试比较它,(2)这是“正确的”STL方式,(3)出现愚蠢错误的机会更少,(4)更容易阅读一旦你可以阅读 STL 代码。学习 STL 编程非常值得,我建议您查看 Scott Meyer 的巨著“Effective STL”,其中包含大量有关此类内容的 STL 技巧。

另一个重要的一点是,通过在操作结束之前不擦除元素,元素不​​需要被打乱。 GMan 建议使用列表来避免这种情况,但是使用这种方法,整个操作是 O(n)。相比之下,Neil 上面的代码是 O(n^2),因为搜索是 O(n),移除是 O(n)。

【讨论】:

  • 我没写完,上面的代码是如果不需要删除指针的话。有时你想要一个共享所有权的指针容器。
  • Gman - 上面答案中的所有代码也有两个循环。您只是看不到 vector::erase() 内的循环。更糟糕的是,这两个循环是嵌套的。
  • 我认为你的 O(n) 计算有点偏离。
  • 首先,模板化删除器函子使这段代码更通用(无论如何每个代码库都应该有一个,它们在很多情况下都很有用)。但第二,你应该小心地做一个两次通过的方法;您在删除其他元素时在数组中留下无效指针。如果其中一个对象析构函数访问数组 -> kaboom
  • 我喜欢这个解决方案,因为: 1. 它是 O(n) 与 O(n^2)。 2. 这是“正确”的方式,因为它将尽可能多的逻辑推送到 STL 实现中,这可能是经过良好测试和调试的。 3. remove_if 方法虽然更快,但将删除条件与删除操作混为一谈,即函子删除对象作为测试它的副作用。在可读性/可维护性方面不可取。此外,具有动作的函子更有可能具有状态(但这里不是这种情况),使它们不适合作为谓词(参见 Josuttis pp. 302)。
【解决方案5】:

主要问题是大多数 stl 容器迭代器不支持向容器添加或删除元素。有些会使所有迭代器无效,有些只会使指向已删除项的迭代器无效。在您更好地了解每个容器的工作原理之前,您必须仔细阅读文档,了解您可以对容器做什么和不能做什么。

stl 容器不强制执行特定的实现,但向量通常由引擎盖下的数组实现。如果您在开头删除一个元素,则所有其他项目都将被移动。如果您有一个迭代器指向其他项目之一,它现在可能指向旧元素之后的元素。如果您添加一个项目,则可能需要调整数组的大小,因此创建一个新数组,将旧的东西复制过来,现在您的迭代器指向旧版本的向量,这很糟糕。

对于您的问题,遍历向量 backwards 并随时删除元素应该是安全的。它也会稍微快一些,因为您不会在稍后要删除的项目周围移动。

vector<Entity*> Entities;
/* Fill vector here */
vector<Entity*>::iterator it;
for(it=Entities.end(); it!=Entities.begin(); ){
  --it;
  if(*(*it) > 1.5f){
   delete *it;
   it=Entities.erase(it);
  }
}

【讨论】:

  • 虽然向后迭代很聪明,但您会遇到两个问题。首先,vector::erase() 使所有迭代器无效,因此您上面的代码使用了无效的迭代器。但是,更紧迫的问题是 vector::erase() 不接受 reverse_iterator!你上面写的代码不应该编译。
  • hmmm,erase 不采用 reverse_iterator 可能是个问题 :) 但是,erase 不会使指向元素的迭代器在元素被擦除之前失效。无论哪种方式,通过编译和工作代码修复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-05
  • 2020-06-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多