【问题标题】:C++ Clearing vector of pointers to dynamically-allocated object causes exception?C ++清除指向动态分配对象的指针向量会导致异常吗?
【发布时间】:2014-05-15 11:11:38
【问题描述】:

我有一个名为ClassA 的课程。这个ClassA 包含一个指向动态分配的ClassB 类对象的指针向量。 ClassA 的析构函数如下所示:

ClassA::~ClassA() {
    for (vector<ClassB*>::iterator i = m_vActiveB.begin(), e = m_vActiveB.end(); i != e; ) {
        vector<ClassB*>::iterator tmp(i++);
        delete *tmp;
        m_vActiveB.erase(tmp);
    }
}

ClassC 中,我有一个ClassA 对象的向量。我目前的情况是这样的:如果我的ClassC 向量中没有任何ClassA 对象,那么一切正常。但是,如果我在那里有对象,然后在ClassC 中对我的向量调用 .erase(),则会引发异常。通过日志记录,我确定调用了ClassA 析构函数(应该如此,因为我的理解是.erase() 破坏了每个向量成员),但是当循环开始时抛出异常。

有谁知道这可能是什么原因造成的?谢谢!

【问题讨论】:

  • 你为什么要持有指向动态分配对象的指针?
  • 我不应该这样做吗? :S
  • @user1014679 好吧,shared_ptrunique_ptr 是你的朋友,如果你使用其中之一,你甚至不必迭代向量来破坏每个成员。
  • @user1014679,没有。请参阅here

标签: c++ exception pointers vector erase


【解决方案1】:

按照rule of zero 并使用以下内容声明您的m_vActiveB 成员向量:

std::vector<ClassB> m_vActiveB;

并让默认构造函数负责销毁。您也可以使用std::unique_ptr 的向量,但是,就像您当前的解决方案一样,您不会利用数组中连续元素的自然效率。

在我的系统中,ClassB 本身的对象需要不断地从向量中添加、删除和更新以保持状态模型。

如果您的数据模型并非自然地基于元素连续且具有特定顺序这一事实,则“未排序”的关联容器(如 std::unordered_mapstd::unordered_set)可能是更好的解决方案,尤其是如果您插入和删除不在向量末尾的元素(而是位于中间)。

您可能还想看看std::liststd::deque,它们分别提供快速插入和删除(不会使迭代器失效)和两端快速插入和删除(不会使迭代器失效)迭代器)。

我记得更改向量内容会使所有指针无效.. 对吗?

如果您调用std::vector::erase 是,所有迭代器都将失效(包括过去的结束迭代器)。调用erase 也会使所有指向已删除对象的引用和指针无效(就像引用任何被销毁/释放的对象都是无效的一样)。

【讨论】:

  • 好吧,似乎对象向量而不是指针是最流行的解决方案。问题:如果我仍然处理指向向量中对象的指针,会有什么复杂的情况吗?我记得更改向量内容会使所有指针无效。对吗?
  • @user1014679 根本不要使用指针,除非你有充分的理由。 (在 2 年内我仍然必须找到一个)。至于第二个问题:“改变向量内容”是什么意思?
  • 那么我将如何编辑矢量存储对象之一?
  • 在我的系统中 ClassB 对象本身需要不断地从向量中添加、删除和更新以保持状态模型。
  • @user1014679 一样吗? std::vector::operator[] 返回一个 non-const 引用。
【解决方案2】:

来自文档:http://en.cppreference.com/w/cpp/container/vector/erase

在擦除点或之后使迭代器和引用无效,包括 end() 迭代器。

这意味着您的 ie 迭代器都已失效。所以不要像你正在做的那样在循环中使用erase

同时考虑:

  1. 使用智能指针,例如 std::shared_ptr,而不是自己进行内存管理。
  2. 查找erase-remove,虽然它在这里不是很有用,因为:
  3. 您正在删除向量中的每个元素。您无需随时删除它们,只需在将它们全部删除后使用m_vActiveB.clear(),但您甚至不需要这样做,因为:
  4. 这是一个析构函数,m_vActiveB 是一个成员。它会为你整理好,不用自己清理。

【讨论】:

    【解决方案3】:

    使用std::vector::erase 会导致在擦除点处及之后的迭代器失效。这样一来,过去的迭代器e 在第一次迭代后不再有效。

    而是在每次迭代时直接与从std::vector::end 返回的迭代器进行比较。但是,在您的示例中,无需删除任何内容,因为 m_vActiveB 是成员,并且在运行 dtor 后将自动销毁(但需要 deleteing 指向 to 的数据)。 p>

    当擦除发生时,迭代器 i 也将失效,因为复制了迭代器 tmp 并擦除它仍然会导致从向量中擦除相同的元素(擦除一个迭代器没有帮助)。

    ClassA::~ClassA() {
        for (vector<ClassB*>::iterator i = m_vActiveB.begin(); i != m_vActiveB.end(); ++i) {
            //vector<ClassB*>::iterator tmp(i++); // Unnecessary
            delete *i;
            //m_vActiveB.erase(i); // Unnecessary as elements will be erased automatically.
        }
    }
    

    等效的 C++11 代码:

    ClassA::~ClassA() {
        for (auto ptr : m_vActiveB) {
            delete ptr;
        }
    }
    

    更好的选择是使用 C++11 智能指针,例如std::unique_ptr,可以自动处理分配和释放。

    class ClassA {
        std::vector<std::unique_ptr<ClassB>> m_vActiveB; // No need for explicit destructor.
        /* ... */
    };
    

    或者,如果您不使用多态性或有其他特定原因使用指针,那么为什么要使用指针呢?只需使用std:vector&lt;ClassB&gt;

    【讨论】:

      【解决方案4】:

      您在遍历向量时从向量中删除元素。这会使迭代器无效。

      应该是这样的:

      ClassA::~ClassA() 
      {
          for (auto e : m_vActiveB)
          {
              delete e;
          }
      }
      

      你不必清除向量,在析构函数完成后它会被销毁。

      【讨论】:

      • 根本不应该是这样的。 OP 应该使用智能指针。
      【解决方案5】:

      当您使用vector::erase 时,它会使向量中的任何其他迭代器无效。

      所以你不能使用“传统的”迭代器循环。

      最简单的方法是 Rakibul Hasan 建议,只需删除所有指针,然后删除 clear 向量。

      【讨论】:

      • 在这种情况下,没有必要清除向量,因为它即将被销毁。
      • 很公平,尽管对于其他正在寻找如何清除拥有其内容的指针向量的人来说;阅读该技术是删除它们然后清除它们对他们很有用。
      猜你喜欢
      • 1970-01-01
      • 2014-05-07
      • 2011-10-28
      • 2012-11-06
      • 2012-07-23
      • 2021-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多