C++ 如何在链表中递归调用析构函数?

C++ 如何在链表中递归调用析构函数?,c++,C++,我在浏览博客文章时发现了这段代码 #include <iostream> #include <string> using namespace std; struct LNode { int data; LNode* next; LNode(int n){data = n; next = nullptr;} void add_to_end(int n) { if (next) next->add_to_en

我在浏览博客文章时发现了这段代码

#include <iostream>
#include <string>

using namespace std;

struct LNode {
   int data;
   LNode* next;

   LNode(int n){data = n; next = nullptr;}

   void add_to_end(int n) {
      if (next)
         next->add_to_end(n);
      else
         next = new LNode(n);
   }
   ~LNode() { cout << " I am from LNode Destructor " << endl; delete next; }
};

int main()
{   
   LNode root(1);
   root.add_to_end(2);
   root.add_to_end(3);
   root.add_to_end(4);

}

出于某种原因,我一直认为我必须使用某种while或For循环遍历链表,然后继续删除使用new动态分配的所有节点。但是在上面的代码中,析构函数是如何被调用四次的,它是如何自动遍历链表的,从而产生这种多米诺效应(完全删除自己分配的内存)。

它基本上是一条链

在调用
delete next时,从根节点(失去范围的节点,因此会自动调用其解构器)开始
,调用
下一个
元素的解构器,依此类推。当
next
NULL
0
nullptr
时,进程将停止

您还可以向节点添加索引,并自行查看销毁顺序

析构函数是如何被调用四次的?它是如何被调用的 自动遍历链接列表

这是一种常见的递归形式


链表是一种递归数据结构

我想您可能有兴趣在(自制且简单的)单链表中看到更多的递归示例


这是简化的ctor。参数a_max指定要创建多少节点元素

LMBM::Node::Node(uint8_t a_max) :
   m_nodeId            (++M_nodeCount),
   m_next              (0), // linked list
   //... other node initializers items
{
   if(a_max > 1)
   {
      uint8_t nmax = static_cast<uint8_t>(a_max - 1);
      m_next = new Node(nmax); // recurse create another Node
      dtbAssert(m_next)(a_max); // confirm 
   }
   else // (1 >= a_max)
   {
      dtbAssert(1 == a_max)(a_max); // at least one node
      // all requested nodes created
   }

   if (1 == m_nodeId) //i.e. the 1st node
   {
      M_firstNode   = this;   // capture list anchor to static
      M_MAX_THREADS = a_max;  // capture list size to static

      // ...  a few more actions
   }

} // LMBM::Node::Node(uint8_t a_max)
main中的代码(带有限制检查)可以传递较大或较小列表的用户值


dtor与您的博客发现非常相似(尽管顺序相反)

此dtor首先旋转到列表的末尾,然后在展开堆栈时执行清理和删除活动


这个init()方法再次使用递归初始化一些资源(信号量等),并首先完成最后一个节点的init()

所有线程都以这样或那样的方式协作


在这一点上,我有N个节点;每个节点有一个线程;线程的作用可以由m_node_id决定;调试器cout可以用1..10性质的线程id而不是系统id来扩充

此代码:

~LNode() { delete next; } // destructor

如果
next
设置为
0
NULL
nullptr
将不会发生任何事情。但是,如果将其设置为以下
LNode
的地址,则会导致以下
LNode的
析构函数运行。它的析构函数也会做同样的事情,导致它下面的
LNode
被销毁,依此类推,直到你遇到一个
next
变量,它等于
nullptr

delete
和析构函数调用是两件截然不同的事情。但每次删除指向
LNode
的(非空)指针时,就会调用它的析构函数

有关详细信息,请参见此问题:

root
实例的析构函数在
main()
结尾超出范围时被调用:

~LNode
析构函数调用
删除下一步依次调用该对象的析构函数,该析构函数依次。。。当
delete next时,整个递归停止NULL
指针上调用code>

调用最后一个析构函数时的调用堆栈如下所示:

root.~LNode()
delete root.next
root.next.~LNode()
delete root.next->next
root.next->next->~LNode()
delete root.next->next->next
root.next->next->next->~LNode()

然后
删除下一步
实际上在
NULL
上被调用,递归停止。

一个小小的澄清:删除下一个
调用会导致从第一个到最后一个在节点上调用析构函数,但实际的存储回收是从最后一个到第一个进行的。即,这是顺序:

 1) destructor invoked on node 1.
 2)   delete called with pointer to node 2.
 3)   destructor invoked on node 2.
 4)     delete called with pointer to node 3.
 5)     destructor invoked on node 3.
 6)       delete called with pointer to node 4.
 7)       destructor invoked on node 4.
 8)         delete called with nullptr.
 9)       destructor call at (7) reclaims node 4 and returns.
10)     destructor call at (5) reclaims node 3 and returns.
11)   destructor call at (3) reclaims node 2 and returns.
12) destructor call at (1) reclaims node 1 and returns.

注意:使用而不是
NULL
。因此,基本上next最初指向根或1(假设next不为NULL,并且存在节点链)(1->2->3),因此根被删除,并成为(2->3)。。我仍然很好奇2的析构函数现在是如何被调用的,因为我还没有将指针提前到2@pokche您有一个对象列表,所有对象都有一个指向下一个对象的
next
指针。在第一个对象上调用
delete
时,其析构函数在其下一个对象上调用delete,该析构函数在其下一个对象上调用
delete
,以此类推。。直到没有下一个对象,因为指针是
nullptr
void LMBM::Node::init(void)
{
   if(m_next)
      m_next->init(); // tail first

   // 1. ALLOCATE resource, such as a semaphore
   m_semIn = new Sem_t; // create 1 semaphore per thread
   dtbAssert(m_semIn)(m_nodeId);

   //std::cout << "m_semIn " << m_nodeId << " = " << (void*)m_semIn << "\n";

   // 2. INITIALIZE default Sem_t ctor is ok

} // void LMBM::Node::init(void)
void LMBM::Node::startApp(void)   // activate threads
{
   if(m_next)
      m_next->startApp(); // recurse to end of linked list

   // 4. start my thread
   m_thread = new std::thread (LMBM::Node::threadEntry, this);
   dtbAssert(m_thread);

   // ... confirm thread running state

} // void LMBM::Node::startApp(void)   // activate threads
~LNode() { delete next; } // destructor
int main()
{   
   LNode root(1);
   root.add_to_end(2);
   root.add_to_end(3);
   root.add_to_end(4);
/* Destructor of root is called here. Imagine this is present here: */
   root.~LNode(); // calling destructor on object root
}
root.~LNode()
delete root.next
root.next.~LNode()
delete root.next->next
root.next->next->~LNode()
delete root.next->next->next
root.next->next->next->~LNode()
 1) destructor invoked on node 1.
 2)   delete called with pointer to node 2.
 3)   destructor invoked on node 2.
 4)     delete called with pointer to node 3.
 5)     destructor invoked on node 3.
 6)       delete called with pointer to node 4.
 7)       destructor invoked on node 4.
 8)         delete called with nullptr.
 9)       destructor call at (7) reclaims node 4 and returns.
10)     destructor call at (5) reclaims node 3 and returns.
11)   destructor call at (3) reclaims node 2 and returns.
12) destructor call at (1) reclaims node 1 and returns.