C++清空动态分配对象指针的向量会导致异常吗?

3

我有一个名为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() 销毁每个向量成员时应该这样做),但循环开始时就会抛出异常。
请问有人知道可能是什么原因导致这种情况吗?谢谢!

你为什么一开始就要持有动态分配对象的指针呢? - Shoe
我不应该这样做吗? :S - Raiden616
2
@user1014679 嗯,如果你使用shared_ptrunique_ptr,它们会是你的好朋友。这样做甚至不需要遍历向量销毁每个成员。 - Massa
@user1014679,不行。请看这里 - Shoe
5个回答

4

遵循零规则,只需使用以下代码声明您的m_vActiveB成员向量:

std::vector<ClassB> m_vActiveB;

让默认构造函数处理销毁。你还可以使用std::unique_ptr的向量,但是与您当前的解决方案一样,您将无法利用数组中连续元素的自然效率。
在我的系统中,ClassB对象本身需要不断地从向量中添加,删除和更新,以保持状态模型。
如果您的数据模型并不自然地基于元素是连续的并具有特定顺序的事实,那么像std::unordered_mapstd::unordered_set这样的“未排序”的关联容器可能是更好的解决方案,特别是如果您插入和删除不驻留在向量末尾(而是位于中间)的元素。
你可能还想看看std::liststd::deque,它们分别提供快速插入和删除(不会使迭代器失效)以及快速两端插入和删除(也不会使迭代器失效)。如果调用std::vector::erase,是的,所有迭代器都将失效(包括结束迭代器)。调用erase还将使对已删除对象的所有引用和指针无效(就像引用任何已销毁/释放的对象一样无效)。

@user1014679,除非你有充分的理由,否则根本不要使用指针。(我已经用了两年时间,仍然没有找到一个好的理由)。至于第二个问题:你所说的“更改向量内容”是什么意思? - Shoe
在我的系统中,ClassB的对象本身需要不断地从向量中添加、删除和更新,以保持状态模型。 - Raiden616
@user1014679 就是这样吗?std::vector::operator[]返回一个非const引用。 - Bartek Banachewicz
@user1014679:是的,你必须小心指针失效的问题。避免这个问题的一种方法是使用“智能指针”-它们可以给你最好的两个世界。 - Puppy
@user1014679,m_vActiveB[i]将返回向存储的第i个元素的引用。您可以随意修改它。当然,要删除它,您应该从向量中删除它(可能使用erase)。 - Shoe
显示剩余7条评论

2

根据文档: http://en.cppreference.com/w/cpp/container/vector/erase

使得被删除点以及之后的迭代器和引用失效,包括end()迭代器。

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

还要考虑:

  1. 使用智能指针,例如std::shared_ptr,而不是自己进行内存管理。
  2. 查找擦除-移除惯用法,虽然它在这里并不是非常有用,因为:
  3. 你正在删除向量中的每个元素。当你删除完它们时,只需使用m_vActiveB.clear()即可,但是你甚至不需要这样做,因为:
  4. 这是一个析构函数,而m_vActiveB是一个成员变量。它会被自动清理,不需要你自己清理。

1

使用std::vector :: erase会导致在删除点及其之后的迭代器无效。因此,第一次迭代后,过末尾迭代器e将不再有效。

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

当发生删除时,迭代器i也将变为无效,因为复制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<ClassB>即可。

0

在迭代过程中从向量中删除元素会使迭代器失效。

应该像这样:

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

你不需要清空向量,因为在析构函数完成后它会被销毁。


这根本不应该是这样的。原帖作者应该使用智能指针。 - Puppy

-1

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

因此,您不能使用“传统”的迭代器循环。

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


在这种情况下,清除向量没有意义,因为它即将被销毁。 - Mike Seymour
还不错,虽然对于其他想要清除拥有自己内容指针的向量的人而言,让他们知道这种技术是先删除所有指针,然后再清空会更有用。 - M.M

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