I'm currently trying to implement data structures from scratch as a way to better understand how they work while learning C++ and how to use raw pointers at the same time, and right now I'm working on a singly linked list:
#include <vector>
#include <iostream>
template <typename T>
struct Node {
T value;
Node* next;
};
template <typename T>
class SLL {
private:
Node<T>* m_head;
Node<T>* m_tail;
std::size_t m_size;
public:
explicit SLL(std::vector<T>& values);
Node<T>* get(int index);
void insert(int index, T value);
void print();
void set(int index, T value);
};
template<typename T>
SLL<T>::SLL(std::vector<T> &values) {
m_size = values.size();
for (int i = 0; i < values.size(); i++) {
switch (i) {
case 0: {
auto tmp0 = new Node<T>{values[0], nullptr};
m_head = tmp0;
m_tail = tmp0;
break;
}
case 1: {
auto tmp1 = new Node<T>{values[1], nullptr};
m_head->next = tmp1;
m_tail = tmp1;
break;
}
default: {
auto tmpDefault = new Node<T>{values[i], nullptr};
m_tail->next = tmpDefault;
m_tail = tmpDefault;
}
}
}
}
template<typename T>
Node<T>* SLL<T>::get(int index) {
if (0 <= index < m_size) {
int i = 0;
Node<T>* tmp = m_head;
while (i != index) {
tmp = tmp->next;
i++;
}
return tmp;
} else throw "Index out of bounds";
}
template<typename T>
void SLL<T>::insert(int index, T value) {
Node<T>* prev = get(index - 1);
Node<T>* next = prev->next;
auto toInsert = new Node<T>{value, nullptr};
toInsert->next = next;
prev->next = toInsert;
}
template<typename T>
void SLL<T>::print() {
Node<T>* tmp = m_head;
while (tmp->next != nullptr) {
std::cout << tmp->value << " ";
tmp = tmp->next;
}
std::cout << tmp->value << std::endl;
}
template<typename T>
void SLL<T>::set(int index, T value) {
if (0 <= index < m_size) {
int i = 0;
Node<T>* tmp = m_head;
while (i != index) {
tmp = tmp->next;
i++;
}
tmp->value = value;
} else throw "Index out of bounds";
}
int main ()
{
std::vector<int> vec{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
SLL<int> sll(vec);
sll.print();
sll.insert(4, 1337);
sll.print();
Node<int>* node = sll.get(4);
std::cout << node->value << std::endl;
sll.set(6, 600);
sll.print();
return 0;
}
There are likely many issues with my code that I'm unaware of, so identifying them would be greatly appreciated.
However, my main question was about the behavior of my code when I instantiated the temp Nodes in the SLL constructor on the stack rather than the heap (I saw there was a problem with this and switched to heap instantiation, but I don't understand what's going on well enough to understand the problem), with something like this:
template<typename T>
SLL<T>::SLL(std::vector<T> &values) {
m_size = values.size();
for (int i = 0; i < values.size(); i++) {
switch (i) {
case 0: {
// auto tmp0 = new Node<T>{values[0], nullptr};
// m_head = tmp0;
// m_tail = tmp0;
Node<T> tmp0{ values[0], nullptr };
m_tail->next = &tmp0;
m_tail = &tmp0;
break;
}
case 1: {
// auto tmp1 = new Node<T>{values[1], nullptr};
// m_head->next = tmp1;
// m_tail = tmp1;
Node<T> tmp1{ values[1], nullptr };
m_tail->next = &tmp1;
m_tail = &tmp1;
break;
}
default: {
// auto tmpDefault = new Node<T>{values[i], nullptr};
// m_tail->next = tmpDefault;
// m_tail = tmpDefault;
Node<T> tmpDefault{ values[i], nullptr };
m_tail->next = &tmpDefault;
m_tail = &tmpDefault;
}
}
}
}
When going through the debugger to figure out what was wrong with this, I saw that every time tmpDefault was instantiated after the first time (so for i = 3, 4, 5...) this would change the value of m_tail and m_tail->next, does this mean tmpDefault is instantiated in the same memory address each time? Also, if I understand correctly, tmpDefault is destroyed as soon as we leave the scope of the default case, so how come I don't see any change in the value of m_tail once I leave the scope in the debugger (shouldn't it be pointing to an unallocated block of memory resulting in value being equal to some random integer)? Thanks!
P.S. I'm not sure what would be a good, descriptive title for this post, so suggestions are welcome.
Related
For this function, the return value type should be Forward_list, but I assign the list with node value type, so what's the alternative so that my_list.head_ returns a Forward_list?
template <typename T>
Forward_list<T> Forward_list<T>::split()
{
Forward_list<T> my_list;
my_list.head_;
my_list.size_;
Node* tmp = head_;
Node* tmp2 = head_;
if(my_list.empty())
{
return *this;
}
tmp = tmp->next;
while (my_list.empty() == false)
{
tmp = tmp->next;
if (tmp == nullptr) break;
tmp = tmp->next;
tmp2 = tmp2->next;
}
tmp2= tmp2->next;
my_list.head_ = tmp2;
tmp2->next = nullptr;
return my_list.head_;
}
Same user and same problem as can be seen here:
Split linked list into half in c++
Mods should act more reasonable.
Same answer:
You did not show your code. That is a pity. So, I needed to stub it.
You have 3 main errors.
Checking for the empty list is wrong. You are checking the just newly create my_list. This will of course always be empty.
The traversing of the list is wrong
The size calculation is wrong.
Please see the corrected code below:
template <typename T>
struct Forward_list {
struct Node {
T data{};
Node* next{};
};
Node* head_{};
unsigned int size_{};
Forward_list<T> split();
void push_back(const T& t);
bool empty() const { return size_ == 0; }
};
template <typename T>
void Forward_list<T>::push_back(const T& t) {
Node* data = new Node{ t,nullptr };
if (head_ == nullptr)
head_ = data;
else {
Node* tmp{ head_ };
while (tmp->next)
tmp = tmp->next;
tmp->next = data;
}
++size_;
}
template <typename T>
Forward_list<T> Forward_list<T>::split()
{
Forward_list<T> my_list;
Node* tmp = head_;
Node* tmp2 = head_;
if (/*my_list.*/empty() == true)
{
return *this;
}
while (tmp->next != nullptr)
{
tmp = tmp->next;
if (tmp->next == nullptr) break;
tmp = tmp->next;
tmp2 = tmp2->next;
}
my_list.head_ = tmp2->next;
tmp2->next = nullptr;
my_list.size_ = size_/2;
size_ = size_ - my_list.size_;
return my_list;
}
int main() {
Forward_list<int> fw;
for (int i{}; i < 3; ++i)
fw.push_back(i);
Forward_list<int> fw2 = fw.split();
}
Checksum:
xXrZx\rZtWR&vf=d"^KgjwCicpXQ"\z87\lm=PbYLi,b;u4T?6.0%RU33!9kGJZ29#FOTrX$m+9l$Qsxi\7jRbJ'#e$I;V#i?%VXs/6n^3.Er:s#VAaqP:&NJS&HJMx3lGDtR84DQ0sqnb%V5&FOfK^,,cB*24mp?Sp5saOQ%rT;peTkW5:XVmSE*MZ8gx,of6p^grL#!hn:wY0E\l7B5brK0c7:Xpwf?UP'd'2Vo3aX2IJd,Dr^rC!u2?=x$OQ#zPkG'N'gDW3a#^b*x6tY8.;#X^L*&7Y7ySc8hRleorzry6o/o'lV5"#'*gKuwLVP:^9THuC23M7t1!$oWn2Vy,:P;b#faTPfoE",#x'j,rT"iX=phBEQ.c!7$PcMZXB$qeIwYKui$7skOdKSaNAr\UqZ%0aOI\MwF.8co4bCE'=Wzm5o2V^njo"P1Tb'?!%e^wF#9PfsYl"iXX#k78d'I3?oCM6&A+Kwao2^M5YU8Y24"JT*g0lr!QDvIXXnYB!Nz$VW\EwuL'Z^n%R+C3MEZwh%#7G'mLqjD/V8?wIa,Nvh*#fJ:eYi0EmhnboEb#;$!T&xGF13jL:FsH#!VEqt%?Dz?!GXl2H/I\Ol%8ESv:pQwel7\DA,\Yrf2KrA"q5s$i/!Iu&O!z3KH6^K#QOG9'2XIxA7&iyxd2HjB!"6a%=n#ykgK#VhyMk?M^v".SmwpZ7ErWitq3bl%bYuxnY=2my%Y+E97&ch;u=CFaqrqQ*k1tPLsD?Zo?F,I27bp9Q/FsO'GXVc,9"F87,0ql$%NPB#jPw+vBIxwSbCQGN,PerVF*8VpKTO'LJmKtS'w^Sm69Ozqb87XBNzNk522l5Ha'=:*0H?G:2bUX8Gr%\PhR"H2lE\0g6%Z$l\b2/LpP:DUaqzHB+;pg\%pU3Moi%bO&alQ#FImfWxSR;X^Vt`!yA1+389dikl,fYdElr6W;dObtIz$jsGx$V^f8zJ'hBYZnO+Ou?ShEJZVHGq
What is wrong with my code in splitting the linked list into half, what causes the error? I kinda get the logic on how to split it, but is this the right implementation?
template <typename T>
Forward_list<T> Forward_list<T>::split()
{
Forward_list<T> my_list;
Node* tmp = head_;
Node* tmp2 = head_;
if (my_list.empty() == true)
{
return *this;
}
while (tmp != nullptr)
{
tmp = tmp->next;
if (tmp == nullptr) break;
tmp = tmp->next;
tmp2 = tmp2->next;
}
my_list.head_ = tmp2->next;
tmp2->next = nullptr;
my_list.size_++;
return my_list;
}
You did not show your code. That is a pity. So, I needed to stub it.
You have 3 main errors.
Checking for the empty list is wrong. You are checking the just newly create my_list. This will of course always be empty.
The traversing of the list is wrong
The size calculation is wrong.
Please see the corrected code below:
template <typename T>
struct Forward_list {
struct Node {
T data{};
Node* next{};
};
Node* head_{};
unsigned int size_{};
Forward_list<T> split();
void push_back(const T& t);
bool empty() const { return size_ == 0; }
};
template <typename T>
void Forward_list<T>::push_back(const T& t) {
Node* data = new Node{ t,nullptr };
if (head_ == nullptr)
head_ = data;
else {
Node* tmp{ head_ };
while (tmp->next)
tmp = tmp->next;
tmp->next = data;
}
++size_;
}
template <typename T>
Forward_list<T> Forward_list<T>::split()
{
Forward_list<T> my_list;
Node* tmp = head_;
Node* tmp2 = head_;
if (/*my_list.*/empty() == true)
{
return *this;
}
while (tmp->next != nullptr)
{
tmp = tmp->next;
if (tmp->next == nullptr) break;
tmp = tmp->next;
tmp2 = tmp2->next;
}
my_list.head_ = tmp2->next;
tmp2->next = nullptr;
my_list.size_ = size_/2;
size_ = size_ - my_list.size_;
return my_list;
}
int main() {
Forward_list<int> fw;
for (int i{}; i < 3; ++i)
fw.push_back(i);
Forward_list<int> fw2 = fw.split();
}
I have written a linked list(which is aimed for the data type int) implementation.
Seems to be working fine except when I try to sort the list in such a way that all the odd
elements should come after all the even elements with the original order of the even and odd numbers preserved.
Upon debugging in MS Visual Studio, I found out that in the oddevenSort() function, the for loop seems to be going on infinitely...as if somehow the tail->next was not being updated to nullptr.
I can't seem to grasp where the error lies in my logic.
#include<iostream>
template<class T>
class SLL_Node
{
public:
T info;
SLL_Node* next;
SLL_Node();
SLL_Node(T el, SLL_Node<T>* n = nullptr);
};
template<class T>
class SLL
{
private:
SLL_Node<T>* head, * tail;
size_t size;
public:
SLL();
~SLL();
bool isEmpty() const;
size_t get_size() const;
void add_to_head(T el);
void add_to_tail(T el);
void delete_at(size_t); //delete at a certain index. Index starting from 1. Throws an error //message if index out of bounds or list empty.
void display()const; //the logic is for mostly primitive data types and not user defined data //types (including classes)
void oddevenSort();
};
template<class T>
bool SLL<T>::isEmpty() const
{
if (tail == nullptr)
return true;
else
return false;
}
template<class T>
SLL_Node<T>::SLL_Node() : next{ nullptr }
{}
template<class T>
SLL_Node<T>::SLL_Node(T el, SLL_Node<T>* n) : info{ el }, next{ n }
{}
template<class T>
SLL<T>::SLL()
{
size = 0;
head = tail = nullptr;
}
template<class T>
void SLL<T>::add_to_tail(T el)
{
++size;
if (!isEmpty())
{
tail->next = new SLL_Node<T>(el);
tail = tail->next;
}
else
head = tail = new SLL_Node<T>(el);
}
template<class T>
void SLL<T>::add_to_head(T el)
{
head = new SLL_Node<T>(el, head);
if (tail == nullptr) //if empty
{
tail = head;
}
++size;
}
template<class T>
void SLL<T>::display()const
{
std::cout << '\n';
for (SLL_Node<T>* tmp{ head }; tmp != nullptr; tmp = tmp->next)
{
std::cout << tmp->info << "->";
}
std::cout << "NULL\n";
}
template<class T>
void SLL<T>::delete_at(size_t index)
{
if (index >= 1 && index <= size) //bound checking
{
if (!isEmpty()) //we dont need is empty since size takes care of that but still adding it for clarity
{
if (head == tail && index == 1) //if list only has one node and we delete head node
{
delete head;
head = tail = nullptr;
}
//otherwise if list more than one node
else if (index == 1) //if deleting head node
{
SLL_Node<T>* tmp{ head };
head = head->next;
delete tmp;
tmp = nullptr;
}
else //deleting other nodes
{
SLL_Node<T>* tmp{ head->next }, * pred{ head };
for (size_t i{ 2 }; i < index; ++i)
{
tmp = tmp->next;
pred = pred->next;
}
pred->next = tmp->next;
if (tmp == tail)
{
tail = pred;
}
delete tmp;
tmp = nullptr;
}
}
}
else
{
std::cout<<"\nError! Either the list is empty or the index entered is out of bounds!\n";
}
}
template<class T>
void SLL<T>::oddevenSort()
{
SLL_Node<T>* t=head;
size_t count{1};
for (; t != nullptr; t = t->next)
{
if (((t->info) % 2) != 0)
{
add_to_tail(t->info);
delete_at(count);
}
++count;
}
}
main:
int main()
{
SLL<int> a;
a.add_to_head(1);
a.add_to_head(2);
a.add_to_tail(3);
a.add_to_tail(4);
a.add_to_head(6);
a.add_to_tail(7);
a.add_to_head(5);
a.display();
//a.oddevenSort();
a.display();
return 0;
}
Consider an example input for oddevenSort a(1)->b(2) ->c(3)->null
on 1st iteration t is pointing to a(1) new node is created with
data 1 which is appended at the end of list like
b(2)->c(3)->d(1)->null.
On 2nd iteration t will point to node b(2) and no changes done
on list.
On 3rd iteration t will point to node c(3) new node is created
with data 3 which is appended at the end of list like
b(2)->d(1)->e(3)->null.
on 4th iteration t will point to d(1) which creates new node at the end of list. Iteration goes on and on recursively without breaking the loop.
Every time you need not to delete and create the new node. You can segregate even and odd nodes and make final list.
Here is the updated snippet
template<class T>
void SLL<T>::oddevenSort()
{
SLL_Node <T>tempOddHeader;
SLL_Node <T> *tempOddPtr = &tempOddHeader;
SLL_Node <T> tempEvenHeader;
SLL_Node <T> *tempEvenPtr = &tempEvenHeader;
SLL_Node<T>* t = head;
tempOddHeader.next = nullptr;
tempEvenHeader.next = nullptr;
while(t)
{
if (((t->info) % 2) != 0) {
//append to the odd list
tempOddPtr->next = t;
tempOddPtr = tempOddPtr->next;
t = t->next;
tempOddPtr->next = nullptr;
}
else {
//append to the even list
tempEvenPtr->next = t;
tempEvenPtr = tempEvenPtr->next;
t = t->next;
tempEvenPtr->next = nullptr;
}
}
tempEvenPtr->next = tempOddHeader.next;
head = tempEvenHeader.next;
tail = tempOddPtr;
}
I am a beginner learning c++, and currently making a singly linked list. I have faced some problems and I thought for a very long time, searched a lot but still do not have an answer for this code so I am begging for some help..
So this is my linked.h
template <class T>
class Node {
public:
T data;
Node<T>* next;
};
template <class T>
class List {
private:
Node<T> *head;
public:
List() : head(NULL) {};
~List() {
Node<T>* ptr, tmp;
for(ptr = head->next; ptr == NULL; ptr = head->next) {
delete ptr;
}
}
List(T* arr, int n_nodes) {
head = NULL;
Node<T> *tmp = head;
for(int i = 0; i < n_nodes; i++) {
Node<T>* node = new Node<T>;
node->data = arr[i];
if(head == NULL) {
head->next = node;
tmp = node;
}
else {
tmp->next = node;
node->next = NULL;
tmp = node;
}
}
}
friend std::ostream& operator<<(std::ostream& out, List<T>& rhs) {
Node<T>* cur = rhs.head;
out << cur;
while(cur != NULL) {
if(cur->next != NULL) {
out << cur->data << ", ";
cur = cur->next;
}
else
out << cur->data << " ";
}
return out;
}
};
and this is my main.cc file.
#include <iostream>
#include "linked.h"
int main() {
int array[5] = {12, 7, 9, 21, 13};
List<int> li(array, 5);
std::cout << li;
return 0;
}
I keep on getting segmentation fault when running the constructor and I don't get why. Where am I making a mistake? Any help would be appreciated!
You could cover the issue with a pointer to pointer:
List(T* arr, int n_nodes)
{
Node<T>** tmp = &head; // tmp *pointing* to uninitialized(!) head pointer
for(int i = 0; i < n_nodes; i++)
{
Node<T>* node = new Node<T>();
node->data = arr[i];
// now the trick:
*tmp = node; // !!!
// you now have assigned the new node to whatever pointer
// the tmp pointer points to - which initially is - guess - head...
// but we now need to advance!
tmp = &node->next;
}
// tmp now points to latestly created node's next pointer
// (or still head, if no nodes where created because of n_nodes == 0)
// be aware that this one still is not initialized! so:
*tmp = nullptr;
}
Your destructor necessarily fails, too:
Node<T>* ptr, tmp;
for(ptr = head->next; ptr == NULL; ptr = head->next)
{
delete ptr; // you delete ptr, but advancing (ptr = head->next)
// is done AFTERWARDS, so you'd access already deleted memory
// undefined behaviour
}
Additionally, you don't delete the head node! And if head is nullptr, you again have undefined behaviour.
Try it this way:
while(head)
{
Node<T>* tmp = head; // need a copy of pointer
head = head->next; // need to advance BEFORE deleting
delete tmp; // now can delete safely
}
I just read up about smart pointers so i wanted to do a real demo example, hence i created the DLL code below, the problem is the nodes are placed properly and all, but nodes memory are not getting freed, not sure what i am doing wrong.
as far as my understanding , when the scope runs out, the nodes must be deleted automatically. please correct me if i am wrong.
Original Code:
#include <iostream>
#include <memory>
using namespace std;
template <typename T>
class DLL {
class Node {
public:
T key;
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev;
Node():key(),next(),prev(){}
Node(T data):key(data),next(),prev(){}
~Node(){
cout << "node deleted \n";
}
};
std::shared_ptr<Node> m_head;
std::shared_ptr<Node> m_tail;
std::size_t length;
public:
DLL() : m_head() ,m_tail() , length(0){
}
virtual ~DLL(){
}
void add_back(T data){
std::shared_ptr< Node > node = std::make_shared<Node>(data);
if(!m_tail){
m_tail = std::move(node);
m_head = m_tail;
}
else{
m_tail->next = std::move(node);
m_tail->next->prev = m_tail;
m_tail = m_tail->next;
}
length++;
}
void add_front(T data){
std::shared_ptr< Node > node = std::make_shared<Node>(data);
if(!m_head){
m_head = std::move(node);
m_tail = m_head;
}
else{
m_head->prev = std::move(node);
m_head->prev->next = m_head;
m_head = m_head->prev;
}
length++;
}
void printNodes(void){
for(std::shared_ptr< Node > temp = m_head; temp ; temp = temp->next) {
cout << temp->key << '\n';
}
}
void addAtPosition(T data , std::size_t pos){
if(pos < 0 || pos >= length) {
throw("Invalid position");
}
if(pos == 0){
add_front(data);
}
else if(pos == length - 1){
add_back(data);
}
else{
std::shared_ptr< Node > temp = m_head;
for(; temp && pos ; temp = temp->next) {
pos--;
}
std::shared_ptr< Node > node = std::make_shared<Node>(data);
std::shared_ptr< Node > prev = temp->prev;
temp->prev = std::move(node);
temp->prev->next = temp;
temp->prev->prev = prev;
prev->next = temp->prev;
length++;
}
}
};
int main(int argc , char** argv){
std::unique_ptr<DLL<int>> m_list = std::make_unique<DLL<int>>();
m_list->add_front(3);
m_list->add_front(2);
m_list->add_front(1);
m_list->add_back(4);
m_list->add_back(5);
m_list->add_back(6);
m_list->addAtPosition(7,0);
m_list->addAtPosition(7,4);
m_list->addAtPosition(7,7);
m_list->printNodes();
return 0;
}
Modified Code:
#include <iostream>
#include <memory>
using namespace std;
template <typename T>
class DLL {
class Node {
public:
T key;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;
Node():key(),next(),prev() {}
Node(T data):key(data),next(),prev() {}
~Node(){cout << "deleted \n";}
};
std::shared_ptr<Node> m_head;
std::weak_ptr<Node> m_tail;
std::size_t length;
public:
DLL():m_head(),m_tail(),length(0){}
void addFront(T data){
std::shared_ptr< Node > node = std::make_shared<Node>(data);
if(length == 0){
m_head = std::move(node);
m_tail = m_head;
}
else{
node->next = m_head;
m_head->prev = node;
m_head = std::move(node);
}
length++;
}
void addBack(T data){
std::shared_ptr< Node > node = std::make_shared<Node>(data);
if(length == 0){
m_head = std::move(node);
m_tail = m_head;
}
else{
node->prev = m_tail.lock();
node->prev.lock()->next = std::move(node);
m_tail = m_tail.lock()->next;
}
length++;
}
void addAtPosition(T data , std::size_t pos){
if(pos == 0){
addFront(data);
}
else if(pos == length){
addBack(data);
}
else if(pos < 0 || pos >= length) {
throw("Invalid position");
}
else{
std::shared_ptr< Node > node = std::make_shared<Node>(data);
std::weak_ptr<Node> temp = m_head;
for(int cnt = 0; cnt < pos ; cnt++){
temp = temp.lock()->next;
}
node->next = temp.lock();
node->prev = node->next->prev;
node->prev = std::move(node);
length++;
}
}
void printNodes(void){
std::weak_ptr<Node> wp = m_head;
for(int i = 0; i < length; i++) {
auto& sp = *(wp.lock());
cout << sp.key;
wp = sp.next;
}
}
};
int main(){
std::unique_ptr<DLL<int>> m_list = std::make_unique<DLL<int>>();
for(int i = 0; i < 10 ; i++)
{
try{
m_list->addAtPosition(i,i);
}
catch(const char* mess){
cout << i <<' '<<mess << '\n';
}
}
m_list->printNodes();
return 0;
}
PS: Based on the input i have edited my code and its now working, but still i feel my methods are doing too much work and there is scope of optimization. can someone help me in optimizing my code using smart pointers. Also i am not trying to implement DLL, i just wrote enough code to get a hands-on feel using the new smart pointers.
You have circular references. You must resolve them using std::weak_ptr to manage the prev pointer.
Having a circular reference means the reference counters in the shared_ptr instances won't ever reach zero. Therefore the objects they point to will never be deleted.