My programming ramblings

C++17 - Implementing a singly linked list with smart pointers

Posted on February 22, 2019 by Paul

In this tutorial, I will show you how to implement a singly linked list data structure in C++17 with smart pointers. This is also useful as an exercise in learning about how you can use smart pointers instead of raw pointers to manage the memory of the list elements.

A singly linked list is a collection of nodes (elements), in which every node contains a data field and a link to the next node. The first node of a singly linked list is usually named head or root. The last element of the list is usually named tail and his next field links to nothing.

An interesting property of the singly linked list is that, in order to visit a certain node, you will need to pass through all the previous list nodes until the searched one. For example, if you want to print the third element of a singly linked list, you will need to start at the list head and pass through the first and second nodes. In other words, you can’t have random access to the singly linked list elements, like you can for example with an array. If you want to read more about the linked lists theory check the books recommended at the end of the article.

For the purpose of this article, we are going to implement a few of the linked list possible operations: insertion, deletion and traversal.

Disclaimer: In C++, in almost all cases, you should consider using a std::vector instead of a list. If you really need a singly linked list, use std::forward_list.

For simplicity, we are going to store integers in the data field of our singly linked list:

As mentioned in the article introduction, we are going to use smart pointers to manage the allocation/deallocation of a list node, see std::unique_ptr for a quick remainder.

Now, that we have the Node struct defined, let’s manually create a few nodes and link them one to another. It is interesting to observe how the smart pointer destroys a node once the smart pointer is out of the scope where it was created. For this, we will temporarily add a dummy destructor to Node that will be removed in the final code:

Please note the way in which we’ve manually linked the nodes: first we create node n0, next we create node n1 and link this to n0 and so on … For the remainder of the article, for simplicity, I will use the term node, technically n0 is a unique pointer to a Node object.

Basically, at the end of the main function the first node in the list, the head node, which in our manually assembled list is n2 goes out of scope, at which point the unique smart pointer that owns the node object calls his destructor. Because of the way the nodes are linked, when you manually delete or when the head node goes out of scope all the nodes in the list are recursively deleted! What do you think will happen if you create a large list of nodes, e.g. a few millions of nodes, and you delete the header or the header goes out of scope ? Because of the recursive destruction of the subsequent nodes there is a good chance that you will get a stack overflow error.

On Linux and macOS you can check the default stack size for an executable with:

1 ulimit -s

This is what I see on a macOS machine:

1 ~ $ ulimit -s2 81923 ~ $

which means that the default stack size for an executable on my machine is 8 MB.

Due to the above, we don’t want the user to be able to manually erase the head of the list. We will create a List struct that will safely manage the list nodes creation and destruction:

Let’s implement first the push method. What we want is to create a new node object and push it to the head of the list. As a side note, in a singly linked list you really want to insert/remove elements to the head of the list for performance reasons, this is an O(1) operation. Technically, it is possible to insert a new node at some position in the list or at the end, but because of the way we traverse the list, by going through all previous elements, this is an O(n) operation that we usually want to avoid.

If you feel adventurous, you can try to trigger a stack overflow due to the recursive nodes destruction. First, remove the dummy destructor from the Node struct, we don’t need a destructor function for this, and also we don’t want to print the Destroy node with data: x message every time a node is deleted. Try this main function:

At this point, it may be useful to overload the ostream operator in order to be able to print the list elements. First we’ll make the operator function a friend of the List class and than we can implement the new function:

At this point, I think it is instructive to compare the running time of our singly linked list implementation, for filling the list with numbers, with the std::forward_list from STL. I did a simple test: push 10 millions numbers, delete the list elements, push another 10 elements. For simplicity, I’ve used the shell time command to measure the running time for both implementations.

Disclaimer:All data and information provided on this site is for informational purposes only. solarianprogrammer.com makes no representations as to accuracy, completeness, currentness, suitability, or validity of any information on this site and will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use. All information is provided on an as-is basis. solarianprogrammer.com does not collect any personal information about its visitors except that which they provide voluntarily when leaving comments. This information will never be disclosed to any third party for any purpose. Some of the links contained within this site have my referral id, which provides me with a small commission for each sale. Thank you for understanding.