我有一种有效的删除方法,如下所示:
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)
为什么不起作用?我很好奇。任何信息都会有帮助。
答案 0 :(得分:5)
这里有两个不同的概念。首先,对您使用的std::vector
进行维护。向量的工作是保存一系列元素,并且在许多方面它实际上并不关心这些元素实际上是什么。从向量的角度看,它的元素将一直存在,直到明确出现并说要摆脱它们为止。对erase
的调用告诉向量“嘿,您知道该元素在那个位置上吗?请删除它。”因此,当您调用erase
时,就是在告诉该向量摆脱其元素之一。
独立地,矢量中存储了一些对象。您正在存储Person *
个对象,它们是指向Person
对象的指针。这些对象(我假设是)被分配了new
,所以每个人本质上都认为“我将永远活着,或者至少要等到有人来找我delete
为止”。如果delete
一个Person对象,则该对象将不存在。但是,Person对象绝对不知道在某个地方有指向人的向量。
为了使所有功能都能按您希望的方式工作,您实际上需要同时使用erase
和delete
的组合(要注意的是,我将在后面提到)。如果您只是erase
来自矢量的指针,那么从矢量的角度来看,所有内容都将被清理(不再保存指向所讨论的Person对象的指针),但是从Person的角度来看,Person对象仍然非常活跃很好,因为您从未说过delete
。如果您只是delete
指针,那么从“人”的角度看,一切都已清理干净(您已经告诉“人”是时候去天上的巨大操场了),但是从“矢量”的角度看,什么也没有添加或删除,因此您现在在向量中有一个悬空的指针。换句话说,第一个选项导致内存泄漏-从未告知过要清除其Person对象的Person对象-第二个选项导致了悬空的指针-曾经是一个人的指针,但是现在一堆可以循环使用的位,但是程序希望如此。
使用您现在拥有的设置,处理此问题的“最佳”方法是使用组合方法。找到要删除的项目时,首先delete
指针,然后调用erase
。这样可以确保对Person进行清理,并确保向量中不再有悬空指针。
但是,正如一些评论者所指出的那样,还有一种更好的方法。使用Person *
类型并通过Person
管理std::shared_ptr
对象,而不是存储Person
和使用原始指针来引用std::shared_ptr<Person>
对象。与仅显示“是的,那边有东西”的常规指针不同,它不会自行执行任何内存管理,而std::shared_ptr
类型实际上拥有它指向的资源。如果您从向量中erase
到std::shared_ptr
,则std::shared_ptr
会说:“好吧,我刚刚被踢出向量,并且如果我是指向{{1 }},我会Person
为您服务。”这意味着您无需做任何自己的内存管理即可清理内容。
总结:
delete
即可从向量中删除一个元素,但是却使Person漂泊在堆中,想知道为什么没人再爱它了。erase
即可将Person对象释放,但在向量中留下了幽灵的指针,这是一个重大危险。delete
和delete
可以解决此问题,但这不是理想的解决方案。erase
代替原始指针可能是最好的选择,因为它可以确保所有正确的std::shared_ptr
都自动发生。希望这会有所帮助!
还有一个简短的附录-您确定代码正确访问了向量的所有元素吗?例如,如果您delete
位于索引0处,则向量的所有其他元素将向后移一个位置。但是随后您的实现将erase
递增为1,这时您跳过了刚刚移回到第一位置的项目。
我会让您考虑如何解决此问题。另一个答案为使用i
提供了一个很好的建议,这是一个很好的解决方案,但是如果您出于自己的兴趣,想要推出自己的版本,则可能需要考虑如何解决上述问题。
答案 1 :(得分:4)
这是一张几乎可以确定至少有一千字的图片的地方。向量正在存储指针,该指针指向(大概)动态分配的对象,如下所示:
因此,绿色框代表矢量本身中的元素。蓝色框代表您的数据对象。我已经分开了第三个,以表示这是我们(最终)要删除的那个事实。
就目前而言,您的代码正在删除一些绿色框。它将蓝色框(您的数据)保留在内存中,但是您不再有指向它的指针:
在这一点上,您是对的,数据不再出现在向量中,所以您的例程已经“起作用”了。问题在于您不再有权访问该数据,因此您已经泄漏了其内存。
(显然)建议您在找到要从列表中删除的对象时,首先应使用3.3.0-alpha03
破坏数据(蓝色框):
... 然后,使用“擦除”从矢量中删除该元素:
在这种情况下,我会不使用delete
。 std::shared_ptr
用于管理具有共享所有权的对象,您所说的任何内容都不表示您正在处理共享所有权。如果必须使用动态分配的对象,并且不想手动管理事物(我同意避免这样做是一件好事),则可以考虑使用shared_ptr
,也可以考虑使用Boost {{ 3}}。
或者,考虑将其更改为std::unique_ptr
(即,将对象直接存储在向量中,而不是存储指向动态分配对象的指针)。至少根据我的经验,这实际上是绝大多数时间的正确答案。如果确实需要确保在调整矢量大小时不要移动std::vector<Person>
对象,请考虑改用Person
。 std::deque<Person>
与您所创建的相当接近,但是编译器通过将多个数据对象(在您的情况下为std::deque<Person>
)放在一个单一的对象中,至少具有优化分配的潜力。内存块,而不是分别分配每个内存块。
在除非找到相反的证据之前,否则正确的答案很可能是Person
,std::vector<Person>
位居第二。对std::deque<Person>
对象的直接动态分配以及一些自动删除操作(最好),这些东西可以自动删除。
答案 2 :(得分:4)
给出的其他答案总结了您在设计方面真正应该做的事情,那就是使用智能指针。
但是,如果您确实确实使用了原始指针,并使用new
分配了这些条目,则无需编写任何循环即可删除和擦除的方法是
delete
delete
元素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对象来自何处以及您要尝试做什么,您可能需要同时做这两项。