Singly linked list assignment operator overload in C++ - c++

I've the following singly linked list implementation.
template <typename T> struct node{
node(T data):data(data),next(nullptr){}
T data;
node<T> * next;
};
template< typename T> class slist{
node<T>* head;
int size;
public:
slist(node<T>* head):head(head), size(0){}
slist(const slist<T>& rhs){
node<T>* temp = rhs.getHead();
node<T>* p = new node<T>(temp->data);
head = p;
node<T>* current = p;
while(temp != nullptr){
current = current->next;
current->data = temp->data;
}
}
~slist(){
if(head == nullptr) return;
while(head != nullptr){
node<T>* current = head;
head = head->next;
delete(current);
}
}
slist& operator= (const slist& rhs){
}
node<T>* getHead()const {
return head;
}
void insertFront(T item){
node<T>* p = new node<T>(item);
if(head == nullptr){
p = head;
size++;
return;
}
p->next = head;
head = p;
size++;
}
void insertBack(T item){
node<T>* p = new node<T>(item);
node<T>* current = head;
while(current->next != nullptr){
current = current->next;
}
current->next = p;
size++;
}
void remove(T item){
bool check = false;
node<T>* current = head;
try {
while(current != nullptr){
if(current->data == item) check = true;
current = current->next;
}
if(!check){
throw std::runtime_error("Item not in list");
}
}catch(std::runtime_error& e){
std::cout<<e.what()<<std::endl;
exit(-1);
}
current = head;
while(current != nullptr){
if(current->next->data == item){
node<T>* temp = current->next;
current->next = current->next->next;
delete(temp);
break;
}
current = current->next;
}
size--;
}
int getSize () const {
return size;
}
void printList(){
node<T>* current = head;
while(current != nullptr){
if(current->next != nullptr){
std::cout<<current->data<<"->";
}else{
std::cout<<current->data<<std::endl;
}
current = current->next;
}
}
};
Based on the current implementation of the class and the copy constructor, can someone help with the assignment operator overload. Also I'm a little confused about the copy constructor and assignment overload. What I understand is that the copy constructor creates a new list which has the same values as the old list from a old list. This the next address for all nodes will be different but the values will be the same since it's a deep copy. Is my understanding correct and then onwards what does the assignment overload do?

If you already have a copy constructor and destructor, also implement swap() and then implement your copy assignment operator in terms of these three, e.g.:
template <typename T>
slist<T>& slist<T>::operator= (slist<T> other) {
this->swap(other);
return *this;
}
Note that the argument is readily copied: unlike the copy constructor the copy assignment can take its argument by value.
With respect of the semantics:
The copy constructor creates a new object with the same value as the original, e.g.:
slist<int> sl1;
// insert elements into sl1
slist<int> sl2(sl1); // uses the copy constructor
The copy assignment replaces the value of ane existing object the value of the assigned value, e.g.
slist<int> sl1;
slist<int> sl2;
// possibly modify both sl1 and sl2
sl2 = sl1; // uses the copy assignment operator

You need to consider these cases:
copy construction, i.e. slist<int> newone = other;
copy assignment, i.e. slist<int> a; /* ... */ a = other;
Now, both of these operations make - as the name suggests - a copy of the original data structure. What exactly that means depends on how you want to implement it, but the following should - to stay close to the principle of least surprise - hold:
slist<int> a = some_generator(), b = another_generator();
slist<int> c = a;
// a == c should be true
b = a;
// a == b should be true now
modify(a);
// a == b should be false now, BUT b should be in the same state as before!
The easiest way to achieve this is by making - as you already suggested - deep copies. Thus you basically do the same as in your copy constructor. You make copies of each node, such that they have the same value as the original nodes, but are different entities.
If you're also targeting "modern C++", that is C++11 an onwards, then there's also a move constructor and a move assignment operator that you might want to implement.
As mentioned in the comments, your deep copy algorithm isn't correct: You need to make a copy of each node:
// if in assigment, delete the nodes pointed to by head first!
node<T> const * iterator = other.getHead();
if (iterator != nullptr) { // in your implementation, head could be a nullptr
node<T> * new_node = new node<T>(*iterator); // make a copy of head
head = new_node;
while (iterator->next) {
iterator = iterator->next;
node<T> * copy = new node<T>(*iterator);
new_node->next = copy;
new_node = copy;
}
new_node->next = nullptr;
}
Also, if you can, prefer smart pointers, unique_ptr in this case, instead of raw pointers.

Related

Why assignment operator working even when it was forbidden?

I'm struggling with OOP again. I tried to implement signgly linked list, here's the code:
template <typename T>
class node
{
T value;
node* next;
public:
node(const T& n)
{
this->value = n;
this->next = nullptr;
}
~node()
{
//is it ok to leave destructor empty in my case?
}
};
template <typename T>
class list
{
node<T>* head;
node<T>* tail;
public:
list()
{
this->head = nullptr;
this->tail = nullptr;
}
list(const list& arr)
{
*this = list(); //<================== the line I mentioned
node* current_this = this->head;
node* current_arr = this->arr;
while (current_arr != nullptr)
{
current_this = new node(current_arr);
current_arr = current_arr->next;
current_this = current_this->next;
}
}
list(list&& arr)
{
this->head = arr.head;
this->tail = arr.tail;
arr = list(); //<================== the line I mentioned
}
~list()
{
node* current = this->head;
while (current != nullptr)
{
node* next = current->next;
delete current;
current = next;
}
}
};
I wrote some lines without thinking, i.e. arr = list() and *this = list(). I didn't implemented operator= so there should be some troubles. I thought compiler has decided to generate copy/move assigment for me, so I decided to add these lines, just for curiosity sake:
list& operator=(const list& arr) = delete;
list& operator=(list&& arr) = delete;
But it still compiled and I don't understand why. What's the deal here? Shoudn't assignment be forbidden and therefore arr = list() and *this = list() be illegal?
Templates aren't evaluated unless/until you instantiate them. If you don't use the copy or move constructor, then their bugs don't produce errors.

C++ linked list Segmentation fault and valgrind errors

i need to code a linked list for university in c++, mostly to practice coding iterators.
I tested it with some basic cases and it works but after i pass it in valgrind and the test server for the program i get a list of different errors. Maybe somebody can help me not to despair.
(At the end i will append the error list)
template <typename T = float>
class ForwardList
{
struct Node
{
/// Constructs a Node from a data value and a link to the next element.
Node(const T &data, Node *next) : data{data}, next{next} {}
/// A Node owns all nodes after it, so it deletes them on destruction
~Node() { delete next; }
//Performs a deep copy of the Node and all Nodes after it. Bad practice but we got it like that
Node *clone() const
{
if (next == nullptr)
{
return new Node{data, nullptr};
}
else
{
return new Node{data, next->clone()};
}
}
T data;
Node *next;
};
public:
ForwardList() : head(nullptr) {}
/// Copy constructor performs a deep copy of the other list's Nodes
ForwardList(const ForwardList &other)
{
head = other.head->clone();
}
/// Destructor makes sure that all Nodes are correctly destroyed
~ForwardList()
{
while (head->next != nullptr)
{
Node *tmp = head;
head = head->next;
delete tmp;
}
delete head;
}
/// Copy assignment operator uses the copy-and-swap idiom to make a safe
/// assignment
ForwardList &operator=(ForwardList other)
{
swap(*this, other);
return *this;
}
/// Add an element to the front of the list.
void push_front(const T &value)
{
std::cout << "Num: " << numberOfNodes << std::endl;
Node *item = new Node(value, nullptr);
if (head==nullptr)
{
head = item;
}else
{
item->next=head;
head = item;
}
numberOfNodes++;
}
/// Remove the first element of the list. Calling this function on an empty
/// list is undefined behavior. When implementing this function, be careful
/// to delete the one and only the one element that is removed.
void pop_front()
{
Node *item;
item = head->next;
delete head;
head = item;
numberOfNodes--;
}
/// Get a reference to the first element of the list
/// (const and non-const version)
T &front()
{
return head->data;
}
const T &front() const
{
return head->data;
}
/// Return true is the list is empty
bool empty() const
{
return numberOfNodes == 0 ? true : false;
}
std::size_t size() const
{
return numberOfNodes;
}
friend void swap(ForwardList &l, ForwardList &r)
{
Node *tmp = l.head;
l.head = r.head;
r.head = tmp;
}
private:
Node *head;
size_t numberOfNodes = 0;
};
And now the fun part (i will put it on pastebin because its pretty long):
https://pastebin.com/4JAKkJtP
Your issue is that ~Node tries to delete its next, and you also try to walk the list in ~ForwardList. By deleting ~Node(), you let ForwardList handle cleanup and everything works.
The clue here is that valgrind reported use after free, meaning something was deleting a pointer twice. That was a clue to look at everything that deletes a Node* (or really, delete in general).

getting segmentation fault on this code for deletion of node in linked list

I'm trying to write a function for the deletion of a node in a linked list , given a double pointer to the head and a pointer to the node to be deleted. (the node to be deleted will not be the tail node)
this is what I have tried:-
public:
int value;
Node* next;
};
/*
headPtr is a reference to the head node(i.e. pointer to pointer) and
deleteNodePtr is the node which is to be deleted. You can see the Node definition above.
It is guaranteed that deleteNodePtr will not point to the last element.
*/
void deleteNode(Node** headPtr, Node* deleteNodePtr) {
Node* current;
current=*headPtr;
if (*headPtr==deleteNodePtr){
*headPtr=deleteNodePtr->next;
//delete current;
return;
}
else {
Node* prev = current;
while(current->next!=deleteNodePtr){
prev = current;
current=current->next;
}
prev->next =current->next;
//delete current;
return;
}
return;
}
I can see multiple things:
1 - In your while condition you are not checking that current is valid:
while(current->next!=deleteNodePtr){
prev = current;
current=current->next;
}
2- What probably is happening to you now: the deleteNodePtr points to the second element in the list, so you never enter to the while loop which means the prev == current which means that you are assigning current->next = current->next.
A solution for this would be:
void deleteNode(Node** headPtr, Node* deleteNodePtr) {
Node* current;
current = *headPtr;
if (current == deleteNodePtr) {
current = deleteNodePtr->next;
delete current;
}
else {
Node* prev = current;
current = current->next;
while (current!= nullptr && current != deleteNodePtr) {
prev = current;
current = current->next;
}
if(current!=nullptr)
{
prev->next = current->next;
delete current;
}
}
return;
}
3- You never checks if headPtr is valid, it couldn't be.
4- It's more a suggestion in the stylish thing rather than a code problem, so if you don't share this point of view, you can freely ignore it. At the very begin you assign *headPtr to current, but instead using current for further usage, you use headPtr. It would be much clear instead, if you always use current:
Original:
Node* current;
current=*headPtr;
if (*headPtr==deleteNodePtr){
*headPtr=deleteNodePtr->next;
//delete current;
return;
}
Suggestion:
Node* current;
current=*headPtr;
if (current==deleteNodePtr){
current=deleteNodePtr->next;
//delete current;
return;
}
This appears at first blush to be a C-style linked list written in C++. It's hard to say for sure since we don't have a bigger picture of your code. A C++ linked list would at least put these functions in a class, and not have a global Node.
The node is what's called an implementation detail. It helps us write the list, but users of the list class should not be able to declare their own nodes, nor should they be aware that nodes exist. Hopefully people using your class aren't calling this delete function explicitly.
Users of your List class might/should (depends) prefer to have iterators available to them. If anything writing iterators for your containers will allow them to be used with range-based for loops. Since your linked list appears to be singly linked, your iterators can only move forward. I also wrote my class with an iterator as I imagine it will help prevent copy/paste homework submissions in most cases.
Now to your function. Per my comment, passing the head as a double pointer makes no sense. Not once do you use it as a double pointer; you always de-reference it. So cut out the middle-man and pass it a node pointer from the get-go.
There's a case that's guaranteed not to happen, and that's that deleteNodePtr will never be the last node. That sounds bad. People can want to delete the last node, but they're allowed and no justification is given.
There's no code to catch an attempt to delete on an empty list.
Your specific issue seems to lie here:
while(current->next!=deleteNodePtr){
prev = current;
current=current->next;
}
You break out of the loop when current->next is the node to be deleted, and not current. You end up deleting the wrong node (current, which is 1 before deleteNodePtr), and that's likely going to cause issues down the line. For instance, if the second node is supposed to be deleted, you end up deleting your head, and that breaks stuff. I imagine that removing the ->next from your Boolean condition would fix the issue. I can't provide a deeper resolution without seeing more of your code.
=== Optional reading ===
Here's an extremely stripped down linked list written as a C++ class. You can run this code here: https://godbolt.org/z/7jnvje
Or compile it yourself.
#include <iostream>
// A simple linked list for integers
class List {
public:
List() = default;
~List();
// List(const List& other); // Copy ctor needed for Rule of 5
// List(List&& other) noexcept; // Move ctor needed for Rule of 5
void push_back(int val);
class iterator;
iterator begin();
iterator end();
iterator find(int val);
void erase(iterator it);
void clear();
// friend void swap(List& lhs, List& rhs); // Not Rule of 5; aids Rule of 5
// List& operator=(List other); // Assignment operator needed for Rule of 5
/*
* Quick note on Rule of 5 functions.
* If your class deals with heap-allocated resources, certain functions become
* required. The only one I included was the destructor. The signature of my
* assignment operator is different than you might see, but the reason is
* it's written to take advantage of the copy/swap idiom.
* https://stackoverflow.com/a/3279550/6119582
*/
private:
// Data
struct Node {
int value = 0;
Node *next = nullptr;
Node(int val) : value(val) {}
};
Node *m_head = nullptr;
Node *m_tail = nullptr;
// Functions
Node *find_node(int val);
};
class List::iterator {
public:
iterator(Node *loc) : location(loc) {}
iterator &operator++();
int operator*();
bool operator==(const List::iterator &rhs);
bool operator!=(const List::iterator &rhs);
private:
Node *location;
};
// List Implementation
List::~List() { clear(); }
void List::push_back(int val) {
if (m_tail) {
m_tail->next = new Node(val);
m_tail = m_tail->next;
return;
}
m_head = new Node(val);
m_tail = m_head;
return;
}
List::iterator List::begin() { return iterator(m_head); }
List::iterator List::end() { return iterator(nullptr); }
List::iterator List::find(int val) { return iterator(find_node(val)); }
void List::erase(iterator it) {
// Emtpy list or end()
if (!m_head || it == end())
return;
Node *toDelete = find_node(*it);
// Deleting head
if (toDelete == m_head) {
m_head = m_head->next;
delete toDelete;
return;
}
// Deleting tail
if (toDelete == m_tail) {
Node *walker = m_head;
while (walker->next != m_tail) {
walker = walker->next;
}
m_tail = walker;
delete m_tail->next;
m_tail->next = nullptr;
return;
}
// Delete any middle node; by moving value until it is the tail, then
// deleting the tail
while (toDelete->next) {
toDelete->value = toDelete->next->value;
if (toDelete->next == m_tail) {
m_tail = toDelete;
}
toDelete = toDelete->next;
}
delete toDelete;
m_tail->next = nullptr;
}
void List::clear() {
while (m_head) {
Node *tmp = m_head;
m_head = m_head->next;
delete tmp;
}
m_tail = nullptr;
}
List::Node *List::find_node(int val) {
if (!m_head) {
return nullptr;
}
Node *walker = m_head;
while (walker && walker->value != val) {
walker = walker->next;
}
return walker;
}
// List iterator implementation
List::iterator &List::iterator::operator++() {
location = location->next;
return *this;
}
int List::iterator::operator*() { return location->value; }
bool List::iterator::operator==(const List::iterator &rhs) {
return location == rhs.location;
}
bool List::iterator::operator!=(const List::iterator &rhs) {
return !(*this == rhs);
}
// Free function
// NOTE: Should take list by const reference, but I didn't add the necessary
// code for that. I'm not passing by value because I also left out Rule of 5
// code that is otherwise required.
// NOTE 2: Could also be templatized and made more generic to print any
// container, but that's outside the scope of this answer.
void print(List &list) {
for (auto i : list) {
std::cout << i << ' ';
}
std::cout << '\n';
}
int main() {
List list;
for (int i = 1; i <= 10; ++i) {
list.push_back(i);
}
print(list);
list.erase(list.find(1));
print(list);
list.erase(list.find(10));
print(list);
list.erase(list.find(6));
print(list);
auto it = list.begin();
for (int i = 0; i < 3; ++i) {
++it;
}
list.erase(it);
print(list);
list.erase(list.find(25)); // Bogus value; could throw if so desired
print(list);
}
Erasing is made much easier with a doubly-linked list, but we don't have one. My erase function makes some checks and handles the head and tail situations individually. For any node in the middle of the list, I don't bother deleting that node specifically. What I do instead is shuffle the value to be deleted to the tail of the list, and then delete the tail.
My comments indicate some things that were left out. I also didn't mark any functions as const. My iterator does not satisfy all requirements of a ForwardIterator. People can probably find other things I left out. I have a couple reasons for this. Mainly that this is quick and dirty code, and I prefer to not provide the temptation of a copy/paste solution.
It would be nice if all C++ instructors would actually teach C++, though. This form of linked list should not be taught in a C++ class anymore.

What i am making wrong with operator overloading?

I don't understand what is wrong when I overload operator +
(purpose of this is to join 2 stacks in one new) ...
it returns "sum" of but change values for those previous.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
classStack
{
private:
struct Node
{
T data;
Node *next;
} *top;
std::size_t size;
public:
Stack();
~Stack();
void push(T data);
void pop(void);
size_t get_size(void);
const Stack& operator=(const Stack &stack_obj);
const Stack operator+(const Stack &stack_obj);
void show_all_stack(void);
};
template <typename T>
const Stack<T> Stack<T>::operator+(const Stack &stack_obj)
{
Stack stack;
Node *tmp;
if (!this->size && !stack_obj.size) {
return stack;
}
if (!stack_obj.size)
{
stack.size = size;
stack.top = top;
}
else if (!size)
{
stack.size = stack_obj.size;
stack.top = stack_obj.top;
}
else
{
stack.size = size + stack_obj.size;
stack.top = new Node;
stack.top = top;
tmp = stack.top;
while (tmp->next)
tmp = tmp->next;
tmp->next = new Node;
tmp->next = stack_obj.top;
}
return stack;
}
Default constructor
template <typename T>
Stack<T>::Stack(void): top(nullptr), size(0)
{
}
Destructor
template <typename T>
Stack<T>::~Stack(void)
{
Node *next;
if (!size)
std::cout << "Stack is empty!\n";
else
{
while (top != nullptr)
{
next = top->next;
delete top;
top = next;
}
top = nullptr;
}
}
Assignment operator
template <typename T>
const Stack<T>& Stack<T>::operator=(const Stack<T> &stack_obj)
{
Node *tmp;
Node *ptr;
Node *last;
Node *new_node;
if (&stack_obj != this)
{
while (top != nullptr)
{
tmp = top;
top = top->next;
delete tmp;
}
top = nullptr;
size = stack_obj.size;
ptr = stack_obj.top;
while (ptr)
{
new_node = new Node;
new_node->data = ptr->data;
new_node->next = nullptr;
if (!top)
{
top = new_node;
last = new_node;
}
else
{
last->next = new_node;
last = new_node;
}
ptr = ptr->next;
}
}
}
Before creating functions that return Stack<T> by value, or have functions that require copy semantics for Stack<T> (as in your operator +), you need to make sure that Stack<T> is safely copyable. Right now, you are lacking a copy constructor, thus your operator + would never work correctly, even if the function itself is bug-free.
You're missing this function:
Stack::Stack(const Stack<T>&)
The easiest way to implement the copy constructor is to take most of your code out of the assignment operator, and place that into the copy constructor. More or less, the code should look something like this (the code has not been compiled or tested -- it is there to illustrate the main point of this answer):
template <typename T>
Stack<T>::Stack<T>(const Stack<T> &stack_obj) : top(nullptr), size(0)
{
Node *tmp;
Node *ptr;
Node *last;
Node *new_node;
ptr = stack_obj.top;
while (ptr)
{
new_node = new Node;
new_node->data = ptr->data;
new_node->next = nullptr;
if (!top)
{
top = new_node;
last = new_node;
}
else
{
last->next = new_node;
last = new_node;
}
ptr = ptr->next;
}
}
You have already written a destructor that should work, so we won't get into that function, but a working destructor is required for the next step -- implementation of the assignment operator.
Now that you have a copy constructor and destructor, you can now write the assignment operator. The technique used here is the copy / swap idiom
#include <algorithm>
//...
template <typename T>
Stack<T>& Stack<T>::operator=(const Stack<T> &stack_obj)
{
if ( &stack_obj != this )
{
Stack<T> temp(stack_obj);
std::swap(top, temp.top);
std::swap(size, temp.size);
}
return *this;
}
Believe it or not, this works. The code that used to be here was moved to the copy constructor, and thus the assignment operator is going to take advantage of the copy constructor.
To explain briefly, the code creates a temporary Stack<T> object from the passed-in stack_obj (this is why you need a working copy constructor). Then all that is done is to swap out the this members with the temporary members. Last, the temp dies off with the old data (this is why the destructor needs to be working correctly).
Note that you need to swap all of the members, so if you add more members to the Stack<T> class, you need to swap them in the assignment operator.
Once you have the basic 3 functions (copy, assign, destroy) written correctly, then and only then should you write functions that return Stack<T> by value, or write functions that take a Stack<T> by value.
You may have other issues with operator + that lie outside the scope of what this answer is presenting to you, but the gist of it is that your class requires that it has correct copy semantics before implementing +.

SinglyLinkedList implementation with rule of 3

I'm new to C++ and have been trying to implement a Singly Linked List, that provides an implementation the destructor, copy constructor and assignment operator. I'm running into compilation issues when trying to implement the copy constructor and the assignment operator.
Here's node.hpp
#ifndef LINKED_LIST_NODE_HPP
#define LINKED_LIST_NODE_HPP
template <typename T>
class Node{
public:
T data;
Node* next;
Node();
Node(T);
Node(const Node&);
~Node();
};
template <typename T>
Node<T>::Node(){}
template <typename T>
Node<T>:: Node(const T data): data(data), next(nullptr){}
template <typename T>
Node<T>::Node(const Node<T>& source) : data(source.data),
next(new Node)
{
(*next) = *(source.next) ;
}
template <typename T>
Node<T>::~Node(){}
#endif //LINKED_LIST_NODE_HPP
This is singly_linked_list.hpp
#ifndef LINKED_LIST_SINGLYLINKEDLIST_HPP
#define LINKED_LIST_SINGLYLINKEDLIST_HPP
#include <iostream>
#include "node.hpp"
template <typename T>
class SinglyLinkedList {
private:
Node<T>* head;
std::size_t count;
public:
SinglyLinkedList();
SinglyLinkedList(const SinglyLinkedList& source);
SinglyLinkedList& operator=(const SinglyLinkedList& source);
~SinglyLinkedList();
void insert(T);
void remove(T);
bool isEmpty();
int length();
void print();
};
template <typename T>
SinglyLinkedList<T>::SinglyLinkedList() : head(nullptr), count(0){}
template <typename T>
template <typename T>
SinglyLinkedList<T>::SinglyLinkedList(const SinglyLinkedList& source){
Node<T>* curr = source.head;
while(curr != nullptr){
Node<T>* p = new Node<T>;
p->data = curr->data;
curr = curr->next;
}
}
//template <typename T>
//SinglyLinkedList<T>::SinglyLinkedList& operator=(const SinglyLinkedList<T>& source){
// //not sure how to implment this.
//}
template <typename T>
SinglyLinkedList<T>::~SinglyLinkedList() {
if(!isEmpty()){
Node<T>* temp = head;
Node<T>* prev = nullptr;
while(temp->next != nullptr){
prev = temp;
temp = temp->next;
delete prev;
}
delete temp;
}
}
template <typename T>
bool SinglyLinkedList<T>::isEmpty() {
return head == nullptr;
}
template <typename T>
void SinglyLinkedList<T>::insert(T item) {
Node<T>* p = new Node<T>(item);
p->next = head;
head = p;
count += 1;
}
template <typename T>
void SinglyLinkedList<T>::remove(T item) {
bool present = false;
if (head->data == item){
Node<T>* temp = head;
head = head->next;
delete(temp);
count -= 1;
return;
}
Node<T>* temp = head;
while (temp->next != nullptr){
if (temp->next->data == item){
Node<T>* removable = temp->next;
temp->next = temp->next->next;
delete(removable);
present = true;
count -= 1;
break;
} else{
temp = temp->next;
}
}
if(!present){
throw std::invalid_argument("item not present in list");
}
}
template <typename T>
int SinglyLinkedList<T>::length() {
return count;
}
template <typename T>
void SinglyLinkedList<T>::print() {
if(isEmpty()){
throw std::invalid_argument("Can't print an empty list!");
}
Node<T>* temp = head;
while(temp != nullptr){
if(temp->next != nullptr){
std::cout<<temp->data;
std::cout<<"->";
}else{
std::cout<<temp->data;
}
temp = temp->next;
}
std::cout<<std::endl;
}
#endif //LINKED_LIST_SINGLYLINKEDLIST_HPP
I've commented out the copy constructor code to make this compile. What is the correct way of doing this? I'm just learning C++.
One issue that introduces complexity is that it is not well defined what the copy constructor of a node should do? Should the next field of the copy point to the next of the original, or it should create a copy of the next and point to that? The former is inadequate and error-prone, the latter would recursively create a copy of the whole list, one node at a time. This will work for lists of small size but will cause stack overflow for lists with many elements due to the depth of the recursive calls.
So to keep things simple, I wouldn't bother with copy constructor of a node.
template <typename T>
class Node {
public:
T data;
Node* next = nullptr;
Node() = default;
Node(const Node&) = delete; // since copying is not well defined, make it impossible to copy a node.
};
Copying a list is a well defined operation, so implementing the copy constructor makes sense. A mistake with your current implementation is that you allocate a new node, only to leak it later (nothing keeps track of the newly allocated node p). What you need looks more like this:
template <typename T>
SinglyLinkedList<T>::SinglyLinkedList(const SinglyLinkedList<T>& source)
: head(nullptr)
, count(0)
{
// deal with the trivial case of empty list
if (source.head == nullptr)
return;
// deal with the case where count >= 1
head = new Node<T>;
head->data = source.head->data;
head->next = nullptr;
count = 1;
Node<T>* lastCopied = source.head; // last node to be copied
Node<T>* lastAdded = head; // last node to be added to the current list
while (lastCopied->next != nullptr)
{
// create new node
Node<T>* p = new Node<T>;
p->data = lastCopied->next->data;
p->next = nullptr;
// link the newly created node to the last of the current list
lastAdded->next = p;
lastAdded = p;
// advance lastCopied
lastCopied = lastCopied->next;
count++;
}
}
Now regarding the assignment operator, luckily you can use the 'copy and swap' idiom that greatly simplifies things.
template <typename T>
SinglyLinkedList<T>& SinglyLinkedList<T>::operator =(SinglyLinkedList<T> source) // note that you pass by value.
{
std::swap(head, source.head);
std::swap(count, source.count);
return *this;
}
My answer would become too long if I tried to explain the copy and swap technique. It is a clever trick to write exception safe code and avoid duplication (implements assignment by using the copy ctor) at the same time. It is worth reading about it here.
Btw, the declaration of your class should look like this
template <typename T>
class SinglyLinkedList
{
private:
Node<T>* head = nullptr;
std::size_t count = 0;
public:
SinglyLinkedList(const SinglyLinkedList& source);
SinglyLinkedList& operator=(SinglyLinkedList source);
// other members here...
};
PS. My code assumes you are using c++11 or a later standard.
I don't like the direction this is headed. I'm going to explain how to do this approach right because it is an excellent lesson on recursion, but because it's recursion it can run the program out of Automatic storage (march off the end of the stack, most likely) with a sufficiently large list. Not cool.
The logic:
Copying a node copies the next node if there is one. This looks something like
template <typename T>
Node<T>::Node(const Node<T>& source) : data(source.data)
{
if (source.next) // if there is a next, clone it
{
next = new Node<T>(*source.next);
}
else
{
next = nullptr;
}
}
This reduces the linked list copy constructor to
template <typename T>
SinglyLinkedList<T>::SinglyLinkedList(const SinglyLinkedList& source){
head = new Node<T>(*source.head); //clone the head. Cloning the head will clone everything after
count = source.count;
}
A helper function may, uh... help here to make the Node copy constructor a bit more idiomatic
template <typename T>
Node<T> * initnext(const Node<T> & source)
{
if (source.next)
{
return new Node<T>(*source.next);
}
else
{
return nullptr;
}
}
template <typename T>
Node<T>::Node(const Node<T>& source) : data(source.data),
next(initnext(source))
{
}
but I don't think you gain much.
So... I don't like the above. What would I do instead? Something a lot like opetroch's solution above, but different enough that I'll write this up.
The node stays brutally stupid. As far as I'm concerned all a Node should ever know is how to store the payload and find other Nodes. This means the linked list should do all of the heavy lifting.
Concept 1: head is nothing but a next pointer. Once you abstract away its differences, unimportant here, you can use it exactly the same way you would next.
Concept 2: If you only know where next points, you have to do a bunch of extra book-keeping to track the previous node to update it's next. But if you take advantage of the previous's next pointing to the current node, you can throw out even more code. By tracking the previous node's next you have all of the information you need.
Concept 3: If you keep a pointer to the previous node's next, you can update that previous node's next any time you want by dereferencing it.
template <typename T>
SinglyLinkedList<T>::SinglyLinkedList(const SinglyLinkedList& obj)
{
Node<T>* tocopy = obj.head;
Node<T>** nextpp = &head; // head is a next. We are now pointing to a pointer to next
while (tocopy) // keep looping until there is no next node to copy
{
*nextpp = new Node<T>(tocopy->data); // copy source and update destination's next
nextpp = &(*nextpp)->next; // advance to point at the next of the node we just added
tocopy= tocopy->next; // get next node to copy
}
count = obj.count;
}
Because this iterates rather than recurses it doesn't eat up Automatic storage (probably the stack) and can keep going until the cows come home.
This logic can also be applied to remove
template <typename T>
void SinglyLinkedList<T>::remove(T item) {
Node<T>** temp = &head; //head is nothing but a next pointer.
// by pointing to where the next is, we don't
// need to track a previous or have special handling
// for the head node
while (*temp){ // because we now have a pointer to a pointer, we need an
// extra dereference
if ((*temp)->data == item){
Node<T>* removable = *temp;
*temp = (*temp)->next;
delete(removable);
count -= 1;
return; // no need for any special magic. Just get out.
} else{
temp = &(*temp)->next; // update the pointer to the next
}
}
// if we got here the node was not found.
throw std::invalid_argument("item not present in list");
}
And following through on head is just a next, we can also gut the destructor:
template <typename T>
SinglyLinkedList<T>::~SinglyLinkedList() {
while(head){ // if head null, list empty
Node<T>* temp = head; // cache so we can delete
head = head->next; // move head
delete temp; //delete removed node
}
}