向量中这种删除方法有什么问题?

时间:2018-07-29 18:35:54

标签: c++

我有一种有效的删除方法,如下所示:

void deleteUserByID(int id, std::vector<Person*>& userList) 
{

    for(int i = 0; i < userList.size(); i++) {

        if (userList.at(i)->getID() == id) {

            userList.erase(userList.begin() + i);
        }
    }
}

但是,我在上述操作之前尝试了以下操作,但无法理解为什么它不起作用。

我不是使用userList.erase(userList.begin() + i);,而是使用delete userList.at(i)

我对C ++有点陌生,并已被指示使用“ delete”关键字删除分配给堆的内存。我认为应该将其从Vector中删除,但这是错误的。

delete userList.at(i)为什么不起作用?我很好奇。任何信息都会有帮助。

5 个答案:

答案 0 :(得分:5)

这里有两个不同的概念。首先,对您使用的std::vector进行维护。向量的工作是保存一系列元素,并且在许多方面它实际上并不关心这些元素实际上是什么。从向量的角度看,它的元素将一直存在,直到明确出现并说要摆脱它们为止。对erase的调用告诉向量“嘿,您知道该元素在那个位置上吗?请删除它。”因此,当您调用erase时,就是在告诉该向量摆脱其元素之一。

独立地,矢量中存储了一些对象。您正在存储Person *个对象,它们是指向Person对象的指针。这些对象(我假设是)被分配了new,所以每个人本质上都认为“我将永远活着,或者至少要等到有人来找我delete为止”。如果delete一个Person对象,则该对象将不存在。但是,Person对象绝对不知道在某个地方有指向人的向量。

为了使所有功能都能按您希望的方式工作,您实际上需要同时使用erasedelete的组合(要注意的是,我将在后面提到)。如果您只是erase来自矢量的指针,那么从矢量的角度来看,所有内容都将被清理(不再保存指向所讨论的Person对象的指针),但是从Person的角度来看,Person对象仍然非常活跃很好,因为您从未说过delete。如果您只是delete指针,那么从“人”的角度看,一切都已清理干净(您已经告诉“人”是时候去天上的巨大操场了),但是从“矢量”的角度看,什么也没有添加或删除,因此您现在在向量中有一个悬空的指针。换句话说,第一个选项导致内存泄漏-从未告知过要清除其Person对象的Person对象-第二个选项导致了悬空的指针-曾经是一个人的指针,但是现在一堆可以循环使用的位,但是程序希望如此。

使用您现在拥有的设置,处理此问题的“最佳”方法是使用组合方法。找到要删除的项目时,首先delete指针,然后调用erase。这样可以确保对Person进行清理,并确保向量中不再有悬空指针。

但是,正如一些评论者所指出的那样,还有一种更好的方法。使用Person *类型并通过Person管理std::shared_ptr对象,而不是存储Person和使用原始指针来引用std::shared_ptr<Person>对象。与仅显示“是的,那边有东西”的常规指针不同,它不会自行执行任何内存管理,而std::shared_ptr类型实际上拥有它指向的资源。如果您从向量中erasestd::shared_ptr,则std::shared_ptr会说:“好吧,我刚刚被踢出向量,并且如果我是指向{{1 }},我会Person为您服务。”这意味着您无需做任何自己的内存管理即可清理内容。

总结:

  • 只需调用delete即可从向量中删除一个元素,但是却使Person漂泊在堆中,想知道为什么没人再爱它了。
  • 只需调用erase即可将Person对象释放,但在向量中留下了幽灵的指针,这是一个重大危险。
  • 以适当的顺序调用deletedelete可以解决此问题,但这不是理想的解决方案。
  • 使用erase代替原始指针可能是最好的选择,因为它可以确保所有正确的std::shared_ptr都自动发生。

希望这会有所帮助!


还有一个简短的附录-您确定代码正确访问了向量的所有元素吗?例如,如果您delete位于索引0处,则向量的所有其他元素将向后移一个位置。但是随后您的实现将erase递增为1,这时您跳过了刚刚移回到第一位置的项目。

我会让您考虑如何解决此问题。另一个答案为使用i提供了一个很好的建议,这是一个很好的解决方案,但是如果您出于自己的兴趣,想要推出自己的版本,则可能需要考虑如何解决上述问题。

答案 1 :(得分:4)

这是一张几乎可以确定至少有一千字的图片的地方。向量正在存储指针,该指针指向(大概)动态分配的对象,如下所示:

enter image description here

因此,绿色框代表矢量本身中的元素。蓝色框代表您的数据对象。我已经分开了第三个,以表示这是我们(最终)要删除的那个事实。

就目前而言,您的代码正在删除一些绿色框。它将蓝色框(您的数据)保留在内存中,但是您不再有指向它的指针:

enter image description here

在这一点上,您是对的,数据不再出现在向量中,所以您的例程已经“起作用”了。问题在于您不再有权访问该数据,因此您已经泄漏了其内存。

(显然)建议您在找到要从列表中删除的对象时,首先应使用3.3.0-alpha03破坏数据(蓝色框):

enter image description here

... 然后,使用“擦除”从矢量中删除该元素:

enter image description here

替代项

在这种情况下,我会使用deletestd::shared_ptr用于管理具有共享所有权的对象,您所说的任何内容都不表示您正在处理共享所有权。如果必须使用动态分配的对象,并且不想手动管理事物(我同意避免这样做是一件好事),则可以考虑使用shared_ptr,也可以考虑使用Boost {{ 3}}。

或者,考虑将其更改为std::unique_ptr(即,将对象直接存储在向量中,而不是存储指向动态分配对象的指针)。至少根据我的经验,这实际上是绝大多数时间的正确答案。如果确实需要确保在调整矢量大小时不要移动std::vector<Person>对象,请考虑改用Personstd::deque<Person>与您所创建的相当接近,但是编译器通过将多个数据对象(在您的情况下为std::deque<Person>)放在一个单一的对象中,至少具有优化分配的潜力。内存块,而不是分别分配每个内存块。

结论

在除非找到相反的证据之前,否则正确的答案很可能是Personstd::vector<Person>位居第二。对std::deque<Person>对象的直接动态分配以及一些自动删除操作(最好),这些东西可以自动删除。

答案 2 :(得分:4)

给出的其他答案总结了您在设计方面真正应该做的事情,那就是使用智能指针。

但是,如果您确实确实使用了原始指针,并使用new分配了这些条目,则无需编写任何循环即可删除擦除的方法是

  1. 将元素划分为delete
  2. delete元素
  3. 使用vector<T>::erase从向量中擦除分区的元素。

这里是一个例子:

void deleteUserByID(int id, std::vector<Person*>& userList) 
{
   // partition the about-to-be deleted elements to the right of the partition
   // and all good items to the left of the partition
   auto iter = std::partition(userList.begin(), userList.end(), [&](Person *p)
                                     { return p->getID() != id; });

   // issue a delete on those elements on right of partition
   std::for_each(iter, userList.end(), [](Person *p) { delete p; });

   // now erase those elements from the vector.
   userList.erase(iter, userList.end());
}

std::partition只是将您要删除的所有元素放在分区的右侧(由iter返回)。然后,只需在分区右侧的那些元素上调用delete,最后删除这些元素即可。

执行此三步过程而不是直接使用std::remove_if的原因是std::remove_if在范围内为您提供了未确定的元素,表示被“删除”的项目,因此发出后续的{ {1}}对这些元素的调用将导致未定义的行为。

例如,此代码即使看起来可行,实际上也会导致未定义的行为:

delete

基本上,您不能对已删除范围内的项目执行任何“特殊”操作(例如,调用void deleteUserByID(int id, std::vector<Person*>& userList) { // move items to be removed to the end of the vector auto iter = std::remove_if(userList.begin(), userList.end(), [&](Person *p) { return p->getID() == id; }); // issue a delete on those elements (this actually invokes undefined behavior) std::for_each(iter, userList.end(), [](Person *p) { delete p; }); // now erase those elements from the vector (if your program even gets this far) userList.erase(iter, userList.end()); } ),因为这些项目是不确定的垃圾。您唯一可以安全做的就是delete

所以诀窍是对元素进行分区(不会使这些项目无效),erase分区后的元素,然后然后使用delete删除它们。

*请注意,如果要保留不会删除的元素的顺序,请使用std::stable_partition而不是erase

答案 3 :(得分:2)

正确的方法是使用智能指针和STL中的算法。

void deleteUserByID(int id, std::vector<std::unique_ptr<Person>>& userList)
{
    auto endIt = std::remove_if(userList.begin(), userList.end(),
        [id](const auto &person) {
            return person->getID() == id;
        });
    userList.erase(endIt, userList.end());
}

答案 4 :(得分:1)

这是两个不同且互补的东西。为您的载体

userList.erase(userList.begin() + i);

将从向量中删除第ith个指针,但不会以任何方式影响指向Person对象的指针

delete userList.at(i);

将删除(释放)向量中第ith个指针指向的Person对象,但不会以任何方式影响向量。

取决于这些Person对象来自何处以及您要尝试做什么,您可能需要同时做这两项。