3

如果我要创建一个节点类,如下所示,并且如果它用于双向链表,它会在解构双向链表时创建一个无限循环吗?或者它会很好地终止吗?

class Node
{
    Node(  );

    ~Node(  )
    {
       delete mNext; //deallocs next node
    }

    Contact mContact;
    Node* mPrevious;
    Node* mNext;
}; 

编辑:如果我将代码修改为这样,它会工作吗?

~Node(  )
{
   mPrevious = NULL;
   if (mNext->mPrevious != NULL)
   {
      delete mNext; //deallocs next node
   }
}

编辑2:或者这会最有效吗?

~Node(  )
{
   if (mPrevious != NULL)
   {
      mPrevious = NULL;
      delete mNext; //deallocs next node
   }
}
4

5 回答 5

2

如果考虑到mNext节点正在形成一个循环的指针,那么任何节点的破坏确实可能会形成一个无限递归循环,并且它将通过炸毁堆栈来终止程序。

它可能会发生的是

  1. 第一个“外部”delete node;发行。
  2. 当进入节点析构函数时,还没有做任何事情,因为代码析构函数是销毁过程中执行的“第一”件事(销毁过程本身非常复杂,包括析构函数代码、类更改、成员销毁、基础销毁,顺序如下:有关更详细的解释,请参阅此答案)。
  3. 第一个析构指令填充执行delete mNext;因此在循环中的下一个节点上触发相同的过程。
  4. 因为节点正在形成一个循环,所以这个链将node再次“从后面”到达,从而使第一次调用成为永远不会结束的递归。
  5. 每次调用都会为激活记录分配堆栈空间,因此一段时间后所有允许用于堆栈的内存将被耗尽,操作系统将终止该进程。删除调用不是“尾调用”,因为在析构函数代码完成后,内存必须被释放,所以这个递归不能轻易地被优化掉......而delete mNext;析构函数的最后一条语句仍然有必须在之后执行的操作delete运算符完成。

但是请注意,根据我的经验,除非您使用特殊的编译器选项,否则不会检查堆栈溢出,因此程序终止将非常“异常”。另请注意,在 Windows 下,有一些可怕的代码在某些情况下会隐藏 segfault 错误,如果它们发生在程序终止时,那么 Windows 程序很可能只是在退出事件循环后完成的此操作中优雅地终止。

假设堆栈溢出通常不被认为确实有任何行为是可能的,包括明显的“无限循环”(请注意,这个无限循环可能不是递归析构函数之一,而是运行时系统内部的某个地方因为堆栈溢出而变得疯狂) .

为什么我用了大概这个词?原因是 C++ 标准规定对象的多次破坏是未定义的行为。如果您将此添加到 C++ 中无法在不完成销毁的情况下退出析构函数的事实中,您将了解编译器理论上允许将对象标记为“正在销毁”并使守护程序飞出您的nosrils 如果你两次输入同一个对象的析构函数。但是,检查此错误不是强制性的,编译器编写者通常很懒惰(这不是对程序员的侮辱),因此不太可能出现此检查(除非启用了某些特殊的额外调试选项)。

总结一下:它可以永远循环吗?是的。能崩溃吗?当然。它可以停止告诉我一个物体被摧毁了两次吗?当然。它可以很好地终止程序(即不设置任何错误代码)吗?是的,那也是。

任何事情都有可能发生。墨菲说它会发生任何会对你造成最大伤害的事情......例如,当你开发它时,程序每次都会很好地终止......并且它会在你的演示日期间严重崩溃在一千名潜在客户面前。

只是不要那样做:-)

于 2011-09-25T06:32:52.317 回答
1

它无法知道何时停止,因此它可能会无限运行。
您可能应该编写一个List类,它有一个指向 a (或实际) 的指针NodeNode在这种情况下, d'tor 应该只处理自己的字段mContactList的 d'tor 应该遍历列表中的所有节点(记住何时停止),并删除每个节点(恰好一次)。

于 2011-09-25T05:57:00.220 回答
1

假设您将 mNext 初始化为 null,它不会无限运行。当遇到空指针时,Delete 什么也不做。因此,它会在您期望的时候结束。

我不确定您在使用“如果以前”选项做什么。那些行不通。这将是一个有效的节点,因此有一个先前的节点,或者它不是一个有效的节点,检查先前的将有未定义的结果。坚持简单的答案:

class Node 
{ 
Node(  mNext = NULL; ); 

~Node(  ) 
{ 
   delete mNext; //deallocs next node 
} 

Contact mContact; 
Node* mPrevious; 
Node* mNext; 
};  

澄清:此解决方案有效,但前提是满足两个条件:1)列表中没有节点出现两次。2)列表不是循环的。如果你能保证这些条件,这是你最简单的答案。如果你不能,你需要做一些更复杂的事情。

于 2011-09-25T06:25:29.160 回答
0

就个人而言,我认为 aNode的析构函数应该与其他节点有任何关系有点奇怪。

如果设计由我决定,我会创建一个List包含指向Node对象(firstlast)的指针的类。该类的析构函数List将负责遍历列表中的所有节点并销毁它们。

于 2011-09-25T07:09:11.700 回答
0

这其实很简单。假设 1)它是一个双链表,而不是一个循环链表 2)链表中没有循环:这是一个双链表 3)实现类只有一个 Node 实例,可能称为 HeadNode 或 LinkList ;)这是显式销毁的节点


示例:LinkList are 1->2->3->4->5->6->NULL HeadNode 的析构函数调用(参考第三个假设)将导致递归调用如下:delete(1)->delete(2 )->delete(3)->delete(4)->delete(5)->delete(6)->NULL 所以请检查 if (mNext != NULL) delete mNext 是否有效 :)


但是:如果你想专门删除一个节点:假设我们想在上面的例子中只删除 4 个,所有的节点都会被删除直到 NULL,所以在删除之前请确保你将 Mnext 设置为 NULL。


最佳实践是使用 STL 库或以其他方式使用自动指针类来解决问题的破坏部分

于 2011-09-25T07:15:35.637 回答