如果一个 std::vector 元素使用 delete this; 来“自杀”,会发生什么?

3
假设有一个 Item 向量。
vector<Item*> items; //{item1, item2, item3}

然后,在代码的其他部分,

items[1]->suicide();

其中 suicide 函数如下:

void Item::suicide()
{
   delete this;
}

什么是 items 向量的大小以及它现在的排列方式?这样做是否可以?
编辑(我可以问一个额外的问题吗?):如果所需输出的排列方式为 {item1,item3},大小为 2,并且没有悬空指针,如何以自毁方式完成(从 item2 本身)?
编辑2:感谢所有的答案!太棒了。因此,我最终决定并找到了从对象外部完成它的方法,因为这是一种不好的实践,也是不必要的复杂。

1
请查看带有标签[self-destruction]的相关问题:http://stackoverflow.com/questions/tagged/self-destruction%20c%2b%2b?sort=votes&pagesize=50 - Daniel Daranas
1
你能做的最好的事情就是重新思考你的问题。你真的需要自杀型对象吗?你能重构它,使得对象告诉调用者它是否需要被销毁,然后由调用者从向量中删除/移除吗? - David Rodríguez - dribeas
7个回答

6
项目向量大小是什么,现在它的排列方式是什么?相同。函数调用根本不会改变向量内容或大小。它只是释放指针所指向的内存。
这样做可以吗?更精确地说:这是合法的C ++吗?是的。编程风格好吗?不。让我详细说明:
- 应该有关注点分离:谁负责内存管理?容器还是类Item的用户或类Item本身? - 通常应该由容器或用户来处理,因为他知道发生了什么。 - 做到这一点的方法是什么?现代和安全的C ++代码中的内存管理主要使用智能指针(如std :: shared_ptr和std :: unique_ptr)以及容器(如std :: vector和std :: map)进行。 - 如果类Item是值类型(这意味着您可以简单地复制它,并且它没有虚函数的多态行为),则只需在代码中使用std :: vector 即可。当从容器中删除元素时,析构函数将自动调用。容器为您完成。 - 如果类Item具有多态行为并且可以用作基类,则使用std :: vector >或std :: vector >,并优先选择std :: unique_ptr解决方案,因为它增加的开销较少。只要您停止引用对象,它就会被智能指针的析构函数自动删除。您(几乎)不需要再担心内存泄漏。 - 您可以产生内存泄漏的唯一方法是具有相互引用的std :: shared_ptrs的对象。使用std :: unique_ptrs可以防止这种麻烦。另一个出路是std :: weak_ptrs。 - 底线:不要提供函数suicide()。而是将责任完全交给调用代码。使用标准库容器和智能指针来管理内存。
编辑:关于您编辑中的问题。只需编写
items.erase( items.begin() + 1 );

这适用于所有类型:std::vector 的值或指针。你可以在这里找到std::vector和C++ Standard library的良好文档。 这里

5
自杀成员不改变向量。因此,向量包含一个无效指针元素,形式上你不能用无效指针做太多事情,即使是复制或比较都是未定义的行为。因此,任何访问它的东西,包括向量调整大小,都是未定义行为。
虽然任何访问形式上都是未定义行为,但只要不引用指针,你的实现很有可能不会表现出奇怪的行为-这样做的原因是在某些机器上,将无效指针加载到寄存器中可能会陷入,虽然x86是其中之一,但我不知道有哪些广泛使用的操作系统以这种方式工作。

访问悬空指针不是未定义行为;解引用一个才是。 - Oliver Charlesworth
1
@OliCharlesworth,我想要一个定义行为或至少留给实现的参考文献。(我从未找到过这样的文献,但我可能错过了,特别是如果它是在C++11中引入的;在x86上加载无效的段描述符肯定是一个陷阱,这种事情已经在Usenet上讨论UBness的众多场合提到过)。 - AProgrammer
将其加载到任意寄存器中? - Oliver Charlesworth
但为什么它会被加载到段寄存器中? - Oliver Charlesworth
为什么不呢?参数传递约定要求指针的段部分通过段寄存器传递是最明显的情况,很难避免。 - AProgrammer
显示剩余5条评论

1

你的suicide函数与Items向量无关,更不知道它的存在。因此从向量的角度来看:当你调用该函数时,没有任何变化,这是可以接受的。


从向量的角度来看,被删除的指针对象现在处于无效状态:它不再可分配。向量处于一种特殊的、几乎无效的状态。(但你仍然可以销毁它,并且你仍然可以将它放回到有效状态。) - curiousguy
“向量仍处于完全相同的状态。”不,向量已经损坏(但可修复),因为它现在包含无效对象。 “指针始终是可分配的。”不,指针无效;你只能给它分配一个有效值。 “但是对我的答案进行负评是太过分了。”我不同意你的答案。 “因为它不包含任何错误陈述。”我认为它确实有误。 - curiousguy
来自n3242:[basic.stc.dynamic.deallocation]/4“如果标准库中的解分配函数所给定的参数是一个非空指针值(4.10),则解分配函数应该释放由指针引用的存储,使得所有指向已释放存储的任何部分的指针都无效。使用无效指针值(包括将其传递给解分配函数)的影响是未定义的3。” - curiousguy
你是否也想证明向量允许复制其元素?(我希望不是; n3242一般容器要求非常混乱。) - curiousguy
这只证明了在调用items[1]->suicide();之后调用delete items[i]是未定义行为,而不是释放向量是未定义行为。或者复制包含无效指针的向量是未定义行为。或者该向量以某种方式处于损坏状态。 - stijn
显示剩余4条评论

1
指针会变得无效,这就是全部。你应该小心不要再次delete它。 vector<Item*> 不会自行删除元素。

具体来说,它将删除指针存储的内存,但不会删除指针所指向的内存。 - Dan
@Dan:我相信你写反了:delete会释放指向的内存,但不会释放指针本身(即它将保留容器不变)。 - David Rodríguez - dribeas
也许我表达不够清晰,我的意思是在向量被销毁时会清除其元素(指针),但不会清除它们所指向的位置。 - Dan

0

哇,看起来你打错了。应该是vector<Item *> Items; 至于你的问题:

  1. 向量Items的大小不变,这意味着它仍然有三个指向Item对象的指针。
  2. 向量的内容未改变: 在执行Items[1]->suicide()之前,Items[0] = 0x000001,Items[1] = 0x000005,Items[2] = 0x000009 执行Items[1]->suicide()之后,Items[0] = 0x000001,Items[1] = 0x000005,Items[2] = 0x000009
  3. 肯定没问题。

此外,当你在容量不足时将一些元素推入向量中时,向量会自动管理其内存,它将重新分配更大的空间,但是,当你弹出一些元素或删除一些元素时,它将永远不会将多余的内存返回给系统。

Items[1]->sucide() 的代码仅会将指针 Items[1] 指向的内存返回给系统,对指针本身不会产生影响,Items[1] 仍然保持原有的值,但指向了一个不安全的区域。

出乎意料的是,您已经创建了一种设计模式。假设您想设计一个类,只允许在堆上分配任何该类的对象,您可以编写以下代码:

class MustOnHeap
{
   private:
      ~MustOnHeap() { // ...}
   public:
      void suicide() { delete this;}

};

因此,该类不能有任何分配在堆栈上的实例,因为析构函数是私有的,编译器必须在对象超出其作用域时安排调用析构函数。 因此,您必须将它们分配到堆上,MustOnHeap* p = new MustOnHeap; 然后显式销毁它:p->suicide();


0

向量不知道您在代码的其他位置正在做什么,因此它将保留对原始Item悬空指针

"这样做可以吗?"

在自杀项目后,您应该手动调整向量,以便不再保留该悬空指针。


除非您想直接将其替换为另一个项目,否则请不要更改它。 - stijn
@stijn:“Replace”很适合“调整”范畴内。 - Johann Gerell
要么彻底删除冒犯索引处的项目(如果极端性能是一个问题,则不建议对 std::vector 进行此操作,因为所有后续项目将被移动,本质上是通过复制可能很大的内存块),要么用有效项目替换它。请注意,空对象模式在这里可能很方便,但您自己的情况可能有所不同。@curiousguy - Johann Gerell
@JohannGerell 在你删除指针后,其值将变为无效。容器包含无效(但可析构)的元素。容器通常假定其元素是有效的。销毁容器应该没问题。"不知道你已经调用了delete" 这就是问题所在:容器仍然假定元素处于有效状态。 - curiousguy
@curiousguy:容器仍然假设元素处于有效状态 - 不,它不是这样假设的。你为什么会这么想呢?如果你在向量中的指针上第二次调用delete,那么这是你的一个bug。如果你在向量上下文之外的指针上调用delete,那么你必须确保向量项也被正确处理,如上所述。没有什么魔法可以发生,你必须自己照顾好它。 - Johann Gerell
显示剩余6条评论

0

如果是指针向量,那么这样做是可以的,因为向量不会调用Item的析构函数。但你必须知道哪些指针仍然有效。

如果你通过值将Items存储在向量中,则调用Item的析构函数是不可取的。当向量被销毁或清除时,它会再次调用item的析构函数,导致应用程序崩溃。


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