how did the destructor got called four times and how did it
automatically traverse through the linked list
This is a common form of recursion.
A linked list is a recursive data structure.
I thought you might be interested in seeing a few more examples of recursion in a (home built and simple) single linked list.
Here is the ctor (simplified). The parameter a_max specifies how many node elements to create.
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)
The above is extracted from running code, but I now consider this code not suitable to ship because when new fails (for any reason) the code asserts. While my dtbAssert provides debugger support, it is not suitable for client use.
Yes, there are no loops here.
Perhaps when you get used to it, the simplicity and ease of use are typical of recursion.
The ctor extends the list with simple new (typically discouraged by many of your peers who prefer a smart pointer).
Each node gets assigned a unique m_node_id to aid debug.
The invocation to create this list is simply:
LMBM::Node nodes(LMBM::Node::DEFAULT_MAX_NODES);
Code in main (with limit checks) can pass in a user value for larger or smaller lists.
The dtor is much like from your blog finding (though in reversed sequence)
LMBM::Node::~Node(void)
{
if(m_next) // delete objects and list
delete m_next; // recurse down the list
// ... clean up actions, if any
m_nodeId = 0;
} // Node::~Node(void)
This dtor first spins to the end of the list, then while unwinding the stack does cleanup and delete activity.
This init() method initializes some resources (semaphores, etc.), again, using recursion, and completing the last node's init() first.
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)
(No loops here, either.)
FYI - LMBM::Sem_t has 4 lines of C++ code, and via the Linux API wraps a single Posix Process Semaphore, set to local mode (unnamed, unshared).
It turns out that this Posix semaphore does work with std::threads as well as posix threads - one of the things I was testing here.
Here is startApp(). I won't show much more than the recursion used... in the example from which I snapshot this code, a thread performs each node's activities.
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
All the threads cooperate in one way or another.
At this point, I have N nodes; each node has a thread; and what the thread does can be determined by m_node_id; and debugger cout's can be augmented with thread id's that are of 1..10 nature instead of the system id's.