I am trying to use smart pointers (std::unique_ptr) to create a singly linked list. Here is an example of a singly linked list with raw pointer.
struct Node {
int data;
Node *next = nullptr;
Node(int data) : data{data}, next{nullptr} {}
~Node() { std::cout << "Destroy node with data: " << data << '\n'; }
};
void print_list(Node *head) {
while (head != nullptr) {
cout << head->data << " --> ";
head = head->next;
}
cout << "nullptr" << std::endl;
}
void insert(Node *&head, int data) {
Node *new_node = new Node{data};
new_node->next = head;
head = new_node;
}
int main(int argc, char *argv[]) {
Node *head = nullptr;
for (int i = 0; i < 5; ++i) {
insert(head, i);
}
print_list(head);
return 0;
}
The output is:
4 --> 3 --> 2 --> 1 --> 0 --> nullptr
Apparently there is memory leak in the above code (destructor is not called). Now I want to use smart pointer to achieve the same thing:
struct Node {
int data = 0;
std::unique_ptr<Node> next;
Node(int data) : data{data}, next{nullptr} {}
~Node() { std::cout << "Destroy node with data: " << data << '\n'; }
};
void print_list(std::unique_ptr<Node> head) {
while (head != nullptr) {
std::cout << head->data << " --> ";
head = std::move(head->next);
}
std::cout << "nullptr" << std::endl;
}
void insert(std::unique_ptr<Node> &&head, int data) {
std::unique_ptr<Node> new_node{std::make_unique<Node>(data)};
new_node->next = std::move(head);
head = std::move(new_node);
}
// g++ -std=c++17 -Wall 2_1.cpp && ./a.out
int main(int argc, char *argv[]) {
std::unique_ptr<Node> head{nullptr};
for (int i = 0; i < 5; ++i) {
insert(std::move(head), i);
}
print_list(std::move(head));
return 0;
}
The output is:
4 --> Destroy node with data: 4
3 --> Destroy node with data: 3
2 --> Destroy node with data: 2
1 --> Destroy node with data: 1
0 --> Destroy node with data: 0
nullptr
We can observe that the life time of new_node ends when insert() returns. I would like to know if it's possible to use smart pointers to achieve singly linked list and retains the functions interface as above.
First thing, there is a problem with your print_list implementation(for both version for unique_ptr only). With your print_list, every time you assign head with a different uniq_ptr, you are actually deallocating the only Node in head, which is not desired. Instead, in your print_list, you should first create a temporary pointer pointing to head, then only iterate on the temporary pointer.
Now onto your unique_ptr version, you don't have to pass a unique_ptr as rvalue reference, you can also pass it as lvalue reference. Instead, your function signature would probably look like:
void print_list(const std::unique_ptr<Node>& head);
void insert(std::unique_ptr<Node> &head, int data);
This allow you to call them without using std::move in your main.
Now on to definitions. For your insertion, what you have is you first create a new Node with the given value, then you assign the old head to new node's next, and make the new node as the new head:
void insert(std::unique_ptr<Node> &head, int data)
{
// Use `auto` to avoid typing `....<Node>` twice
auto new_node = std::make_unique<Node>(data);
new_node->next = std::move(head);
head = std::move(new_node);
}
Alternatively, you can also add one more parameter to Node's constructor:
Node(int data, std::unique_ptr<Node>&& next = nullptr)
: data{data}, next{std::move(next)}
{}
Now you can simply create new_node like:
void insert(std::unique_ptr<Node> &head, int data)
{
// No need to assign `Node::next` separately
auto new_node = std::make_unique<Node>(data, std::move(head));
head = std::move(new_node);
}
Or even assign the new node to head directly:
void insert(std::unique_ptr<Node> &head, int data)
{
head = std::make_unique<Node>(data, std::move(head));
}
For print_list, we should first create a temporary pointer that points to the underlying object of head, then iterate the list by assigning the temporary pointer to its next object's underlying object:
void print_list(const std::unique_ptr<Node>& head)
{
// Create a pointing to the underlying object
// You can use `.get()` to get the underlying pointer
auto current = head.get();
// No need to explicit compare pointer types to `nullptr`
while (current) {
std::cout << current->data << " --> ";
// Make `current` point to the next underlying object
current = current->next.get();
}
std::cout << "nullptr" << std::endl;
}
Now your main would look like:
int main(int, char *[]) {
std::unique_ptr<Node> head;
for (int i = 0; i < 5; ++i) {
insert(head, i);
}
print_list(head);
return 0;
}
Demo
Related
I'm currently working on a class project where we make a linked list and we're supposed to create a function that clears the list then deletes it (with "delete LIST_NAME;"). I have implemented the function as instructed by my professor, also forcing the list to become null after the delete. The function works within itself, but when it returns to the main function, the list gets a new value.
Is this sort of function just not possible in C++?
#include <iostream>
struct Node
{
int val;
Node* next;
};
struct LinkedList
{
int count;
Node* head;
Node* tail;
};
void Clear(LinkedList* list) {
Node* node = list->head;
Node* next = nullptr;
while (node != nullptr) {
next = node->next;
delete node;
node = next;
}
list->head = nullptr;
list->tail = nullptr;
list->count = 0;
}
void Destroy (LinkedList* list) {
Clear(list);
delete list;
list = nullptr;
std::cout << "\n(should be) Destroyed";
}
int main() {
//creating a list element
Node* node = new Node;
node->val = 'a';
node->next = nullptr;
//inserting the element onto list
LinkedList* list = new LinkedList;
list->count = 0;
list->head = node;
list->tail = node;
std::cout << "\nList: " << list;
Destroy(list);
std::cout << "\nList: " << list;
std::cout << "\nEND";
}
This is just a snip of my code but it shows what I mean. Using the debugger the list has the value 0x0 by the end of the function but in the main function it's assigned a new value as shown by the debugger.
You take list by value so it's local to the function.
If you'd like to make changes to it that are visible at the call site, take it by reference:
// `list` is now a reference to the pointer at the call site:
void Destroy(LinkedList*& list) {
Clear(list);
delete list;
list = nullptr; // this now sets the referenced `LinkedList*` to `nullptr`
std::cout << "\n(should be) Destroyed";
}
How to reverse linked list using double pointer?
I was learning about double pointers and thought if we can reverse linked list using one pointer only.
Conversion to using a pointer to pointer left as an exercise for the reader. Also has some distinct shortcomings in terms of style.
#include <iostream>
struct node {
int data;
node *next;
};
node *reverse(node *list) {
node *prev = NULL;
node *next;
while (list) {
next = list->next;
list->next = prev;
prev = list;
list = next;
}
return prev;
}
void show_list(node *list) {
while (list != NULL) {
std::cout << list->data << ", ";
list = list->next;
}
}
int main() {
node *list = NULL;
for (int i=0; i<10; i++) {
node *n = new node;
n->next = list;
n->data = i;
list = n;
}
std::cout << "As built: ";
show_list(list);
list = reverse(list);
std::cout << "Reversed: ";
show_list(list);
return 0;
}
If you decide to modify a pointer you received as a parameter, it's probably easier to deal with a reference to a pointer than a pointer to a pointer though.
I'm not much familiar with C++/pointers but trying to implement a singly linked list.
I'm simply creating a Node (head) and adding Node after head each time a new one is added to the list.
struct Node {
int key;
Node *next;
Node() : key(-1), next(nullptr) { }
Node(int k) : key(k), next(nullptr) { }
};
void AddNode(Node *head, int key) { // Create a new node & add it after the head
Node newNode(key);
newNode.next = head->next;
head->next = &newNode;
}
void PrintNode(Node *nptr, string pre, string post) {
cout << pre << "(" << nptr << "), " << nptr->key << ", " << nptr->next << post;
}
void PrintLL(Node *nptr) {
if (nptr) {
PrintNode(nptr, "\n", "");
nptr = nptr->next;
while (nptr) {
PrintNode(nptr, " -> ", "");
nptr = nptr->next;
}
}
cout << endl;
}
int main()
{
Node n1(1); // Node(1) or head
Node *head = &n1;
AddNode(head, 2); // Node(2)
PrintLL(head); // Node(2) gets modified with this call in VS 17
AddNode(head, 3); // Node(3) turns out to be Node(2) with 3 as key in MinGW
PrintLL(head);
return 0;
}
When I run this program in VS 2017, this throws exception. Debugging shows that the Node(2) gets added correctly after head(Node(1)) but when PrintLL() is called Node(2)'s key gets changed to some random number & next from NULL to 0xcccccccc.
When this program is compiled using MinGW and run, it runs but assigns Node(2) & Node(3) same memory(?) as this output suggests -
(0x71fe30), 1, 0x71fdf0 -> (0x71fdf0), 2, 0
(0x71fe30), 1, 0x71fdf0 -> (0x71fdf0), 3, 0
I'm not sure what I'm missing and unable to figure out too. Please help.
Thanks.
You have a dangling reference in AddNode(). Node newNode(key); is a local variable that cease to exist after AddNode() returns. Hence, head->next points to nowhere. Either manually allocate on heap using new or, better, use a smart pointer like std::unique_ptr.
Node and AddNode could look like this:
struct Node {
int key;
std::unique_ptr<Node> next;
Node(int k = -1, std::unique_ptr<Node> n = {})
: key(k), next(std::move(n))
{ }
};
Node& AddNode(Node& head, int key)
{
head.next = std::make_unique<Node>(key, std::move(head.next));
return *head.next;
}
Edit. Please note the first comment below about a potential pitfall of this approach - stack overflow during automatic list deallocation.
I am trying to teach myself c++ and I am really confused with linked lists. I have been reading through textbooks and looking online but I am really confused how they work. I found an exercise online that I have been trying to figure out, but I am not getting anywhere with it.
Here is the list.h file:
struct node
{
int val;
struct node *next;
};
int length(struct node *);
void push(struct node **, int); //add to front of list
void append(struct node **, int); //add to rear of list
void print(struct node *, int);
I am having a hard time trying to write the functions for length, push and append.
A linked list is simply a string of Node classes string together, with each owning a pointer whose address is that of the next Node class in the list.
It's pretty simple if you think of it this way:
Coliru: http://coliru.stacked-crooked.com/a/5e71c5e31b58673c
#include <iostream>
//Proper encapsulation is not included for terseness and clarity
class Node {
public:
int num;
Node* next;
Node(int n) :
num(n),
next(nullptr) {
};
~Node() {}
};
int main() {
Node a(0);
Node b(1);
Node c(2);
Node d(3);
Node e(4);
//String the nodes together, linking like metal chainlinks
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
//Can you see how the "link" actually works here?
//Each Node's "next" pointer points to the next node.
std::cout << "Node a: " << a.num << std::endl;
std::cout << "Node b: " << a.next->num << std::endl;
std::cout << "Node c: " << a.next->next->num << std::endl;
std::cout << "Node d: " << a.next->next->next->num << std::endl;
std::cout << "Node e: " << a.next->next->next->next->num << std::endl;
//What if I were to point e to the start of a?
e.next = &a;
std::cout << "Node e->next: " << e.next->num << std::endl;
//It's node a!
//Node e.next is now accessible through the linked list:
std::cout << "Node e->next = a.next->next->next->next->next: " << a.next->next->next->next->next->num << std::endl;
//Usually people just use a for loop for this type of stuff.
//Let's use a lambda function to write one right here:
auto index_func = [](Node* head, size_t index) {
Node* current = head;
Node* next = head->next;
for (int i = 0; i < index; ++i) {
if (next != nullptr) {
//Hey, look at the pointers fly!
current = next;
next = current->next;
} else {
std::cout << "Whoops, we hit a null pointer before we got to our index!" << std::endl;
break;
}
}
return current->num;
};
//This is the same as finding the 6th node in the list (but since it's zero-indexing it's [5])
std::cout << index_func(&a, 5) << std::endl;
//We can also continue to do this:
std::cout << index_func(&a, 499) << std::endl;
//This is me accessing the 500th element, which, since our back links to our head node, it's the 4th node, d.
return 0;
}
You can probably imagine the other shenanigans we can do with linked lists if we decide to insert a Node between a or e simply be reassigning pointers.
void push(struct node **list, int newVal) { //add to front of list
struct node* firstNode = *list;
struct node* newNode = (struct node*) malloc(sizeof(struct node));
if (newNode == NULL) abort();
newNode->val = newVal;
newNode->next = firstNode;
*list = newNode;
}
void append(struct node **list, int newVal){ //add to rear of list
if (*list == NULL) {
push(list, newVal);
return;
}
/* Locate last node in list*/
struct node* curNode = *list;
while (curNode->next != NULL)
curNode = curNode->next;
/* Add a new node at the end of the list */
struct node* newNode = (struct node*) malloc(sizeof(struct node));
if (newNode == NULL) abort();
newNode->val = newVal;
newNode->next = NULL;
curNode->next = newNode;
}
This is simple adding nodes to linked list. I'm not able to figure out why the head pointer is being set to null with every call to add function.
//struct declaration of node
struct node {
int data;
node* next;
};
//adding node to the head pointer
void add_node(node* head, int d)
{
node* temp = new node;
temp->data = d;
temp->next = NULL;
node* tmp = head;
if (tmp != NULL) {
cout << "shal";
while (tmp->next != NULL)
tmp = tmp->next;
tmp->next = temp;
}
else {
//cout<<temp->data;
head = temp;
}
cout << "dh" << head->data;
}
int main()
{
node* head = NULL;
// calling the add function
add_node(head, 10);
// head is being taken as null here
add_node(head, 20);
}
Output:
dh10nulldh20null
Please help me in understanding where it went wrong.
I guess you didn't get what a pointer is.
void plus_one(int num) {
num += 1;
}
int main() {
int num = 42;
plus_one(num);
std::cout << num << std::endl;
}
Obviously, num is still 42. Why? Because in function plus_one you get num by copy.
When you call your add_node, you send a copy of your head pointer. Since it is a pointer, you can modify what is POINTED BY the pointer, NOT the pointer itself. What you do is the same thing as trying to get 43 with my example... It's not possible if you are getting a copy.
You need to pass the address of your pointer, so call your function as it : add_node(&head, 10); and write your prototype as it : void add_node(node** head,int d). You will have to modify your function to fit with your new node**.
Why does it work? Because you modify the content of the pointer which is POINTING TO you original pointer (which is POINTING TO your structure).