I am hoping someone can help me. I'm struggling to correctly implement the push_back and pop_front methods for a custom list I'm making. When I run my main program it freezes and windows reports it stops working. This list is being used to build a queue. I have already made my queue class and tested it with the stl list (for my assignment I need to make a custom list as well) so I am fairly certain the problem lies in my list. I think I have not coded push_back and pop_front correctly. Sorry if this is a dumb question, I tried searching for cases similar to mine but I couldn't find any. I would appreciate any help.
my node classs
template<typename T>
class cNode{
public:
T nodeVal;
cNode<T> *next;
cNode<T> *prev;
cNode<T>();
cNode<T>(const T& v, cNode<T> *n, cNode<T> *p);
};
//Methods
//defualt constructor
template<typename T>
cNode<T>::cNode(){
};
//constructor with value value next and prev
template<typename T>
cNode<T>::cNode(const T& v, cNode<T> *n=NULL, cNode<T> *p=NULL){
nodeVal=v;
next=n;
prev=p;
};
and my list, I commented out the other methods because they are not being using is the queue class
#include "cNode.h"
using namespace std;
template <typename T>
class cList{
private:
cNode<T> *frontNode;
cNode<T> *backNode;
int sizeOfList;
public:
cList();
bool empty();
// void push_front(const T& val);
void push_back(const T& val);
void pop_front();
// void pop_back();
T front();
// T back();
int size();
};
//Methods
//Constructor
template <typename T>
cList<T>::cList(){
frontNode = NULL;
backNode = NULL;
};
//Returns true if empty
template<typename T>
bool cList<T>:: empty(){
return frontNode == NULL;
};
//Adds to the back of the list
template<typename T>
void cList<T>:: push_back(const T& val){
cNode<T> *newNode;
newNode = new cNode<T>;
newNode->nodeVal=val;
//inserting in place
newNode->prev = backNode->prev;
newNode->next = backNode;
backNode->prev->next = newNode;
backNode->prev = newNode;
//update size
sizeOfList++;
};
//Removes from the front of the list
template<typename T>
void cList<T>:: pop_front(){
cNode<T> *df;
df = new cNode<T>;
df = frontNode;
df->next->prev=df->prev;
frontNode=frontNode->next;
delete df;
//update size
sizeOfList--;
};
//Returns value of of the front
template<typename T>
T cList<T>:: front(){
return frontNode->nodeVal;
};
//Returns the size of the list
template<typename T>
int cList<T>:: size(){
return sizeOfList;
};
You will find a diagram very useful here - drawing out pointers is one way to be absolutely sure what is referencing what.
That said, here are a few things that jump out:
you initialize frontNode and backNode to NULL in the constructor. What will happen when you try to dereference these NULLs during your very first push_back operation?
related: what value will frontNode have after a single push_back operation? And what value should it have?
what will happen if you try to pop an item off of an empty list?
at each end of the list, the end node should have one of prev and next be NULL or another value indicating that they point nowhere.
The main points here are that you've got a lot of NULLs being dereferenced and you're not updating everything you need to be. The way to wrap your head around the problem is to make a diagram with arrows and boxes and walk through, step by step, what needs to happen when you start with an empty list, add two or three nodes to it, and then successively pop them off.
What is spotted right after looking at your code,
you init backNode and frontNode with NULL value,
but after that in push_back you use operator-> for them,
you need allocate memory for them, before usage.
Little modification of your algorithm to make it works:
#include <cassert>
#include <cstdlib>
template <typename T> struct cNode {
T nodeVal;
cNode<T> *next;
cNode<T> *prev;
cNode<T>(const T &v = T(), cNode<T> *n = NULL, cNode<T> *p = NULL)
: nodeVal(v), next(n), prev(p) {}
};
template <typename T> class cList {
private:
cNode<T> head_;
size_t sizeOfList_;
typedef cNode<T> NT;
public:
cList() : sizeOfList_(0) {
head_.next = &head_;
head_.prev = &head_;
}
~cList() {
for (NT *p = begin(); p != end();) {
NT *next = p->next;
delete p;
p = next;
}
}
cNode<T> *cbegin() const { return head_.next; }
cNode<T> *begin() { return head_.next; }
cNode<T> *end() { return &head_; }
bool empty() const { return head_.next == &head_; }
void push_back(const T &val) {
NT *newNode = new NT(val);
NT *prev_end = end()->prev;
prev_end->next = newNode;
newNode->prev = prev_end;
newNode->next = end();
end()->prev = newNode;
++sizeOfList_;
}
void pop_front() {
if (empty())
return;
NT *next_in_list = begin()->next;
NT *prev_in_list = begin()->prev;
delete begin();
head_.next = next_in_list;
if (prev_in_list == end())
end()->prev = end();
--sizeOfList_;
}
T front() const {
assert(!empty());
return cbegin()->nodeVal;
}
size_t size() const { return sizeOfList_; }
};
int main() {
cList<int> l;
assert(l.size() == 0);
assert(l.empty());
l.push_back(10);
assert(!l.empty());
assert(l.size() == 1);
assert(l.front() == 10);
l.pop_front();
assert(l.size() == 0);
assert(l.empty());
for (int i = 5; i < 17; ++i)
l.push_back(i);
assert(l.size() == (17 - 5));
assert(l.front() == 5);
assert(!l.empty());
{
cNode<int> *p;
int i;
for (p = l.begin(), i = 5; p != l.end(); p = p->next, ++i) {
assert(p->nodeVal == i);
}
assert(i == 17);
}
l.pop_front();
assert(l.size() == (17 - 5 - 1));
assert(l.front() == 6);
assert(!l.empty());
l.pop_front();
assert(l.size() == (17 - 5 - 2));
assert(l.front() == 7);
assert(!l.empty());
}
Related
I am learning about linked lists, and I decided to try implementing linked lists in C++ on my own.
I made a Node class with attributes int val and Node* ptr.
Then I made a Linked_list class with the attribute first_node, and the constructor functions work.
The append() function 'appends' a node to the list (like in Python). I first thought of just making ptr a reference to the node's pointer and then changing it when its null, but references once made, can't be changed to refer to any other variable, so I made another variable prev_ptr that points to the Node's pointer (which makes it a Node**).
Every loop, it checks if ptr is NULL, if not, ptr and prev_ptr get updated to the next Node's pointer value, and the address of the next Node's pointer value, respectively.
This keeps happening until it finds a null pointer, and then changes it to the inputted node's address.
But I'm getting an error saying:
Exception thrown: write access violation. prev_ptr was 0x4.
I can't figure out what is wrong.
Classes:
#include <iostream>
#include <string>
#include <cmath>
class Node {
public:
int val;
Node* ptr = nullptr;
Node(int Val = NULL) {
val = Val;
}
};
class Linked_list {
public:
Node first_node;
Linked_list(int F) {
Node f(F);
first_node = f;
}
void append(Node& element) {
Node* ptr = first_node.ptr;
Node** prev_ptr = &first_node.ptr;
while (true) {
if (ptr == nullptr) {
*prev_ptr = &element;
break;
}
ptr = (*ptr).ptr;
prev_ptr = &((*ptr).ptr);
}
}
};
main()
int main() {
Linked_list list(5);
Node three(3);
list.append(three);
Node four(4);
list.append(four);
return 0;
}
ptr = (*ptr).ptr;
prev_ptr = &((*ptr).ptr);
First you advance ptr to the next node. Then, you use ptr again forgetting that it has already been advanced: (*ptr).ptr now points two nodes forward, and we don't know if we can go that much far.
Perhaps you need to swap the assignments.
prev_ptr = &((*ptr).ptr);
ptr = (*ptr).ptr;
(Further, why not ptr->ptr?)
Okay, you're doing a few things in an odd fashion. First, your Linked_List should probably NOT have a Node for firstNode. It should have a Node *. After all, an empty list is possible. So is (normally) deleting the first node. Also, there's an informal naming convention of calling it head. There's also a standard convention of calling the link in your Node next rather than ptr.
But there are two simpler methods for your append() method. First, you can also keep a Node * tail in Linked_List. This is common. It points to the last node in the list. If you do that, then append looks like:
void append(Node &nodeToAppend) {
if (head == nullptr) {
head = &nodeToAppend;
tail = &nodeToAppend;
}
else {
tail->next = nodeToAppend;
tail = &nodeToAppend;
}
}
However, it's also worthwhile to be able to insert anywhere or append without a tail:
void append(Node &nodeToAppend) {
if (head == nullptr) {
head = &nodeToAppend;
}
else {
Node *ptr = head;
while (ptr->next != nullptr) {
ptr = ptr->next;
}
ptr->next = &nodeToAppend;
}
}
An insert in some sort of sorted order would be nearly identical, although slightly different. The while-loop would look like:
while (ptr->next != nullptr && ptr->value < nodeToAppend.value) ...
but would otherwise be identical.
This code doesn't solve your immediate issue, but answers a question raised in the comments.
Linked lists are usually (where I taught) a 300-400 level assignment. There are a lot of principles that one must be competent in to write a decent linked list. First, I'll show the main.cpp and its output.
main.cpp
#include "list.hpp"
#include <iostream>
template <typename Container>
void print(Container& container, std::ostream& sout = std::cout)
{
for (auto i : container) {
sout << i << ' ';
}
sout << '\n';
}
int main()
{
List<int> list;
for (int i = 1; i <= 10; ++i) {
list.push_back(i);
}
print(list);
list.erase(list.find(4));
print(list);
list.erase(list.find(1));
print(list);
list.erase(list.find(10));
print(list);
}
Output:
1 2 3 4 5 6 7 8 9 10
1 2 3 5 6 7 8 9 10
2 3 5 6 7 8 9 10
2 3 5 6 7 8 9
It doesn't test every aspect of the linked list, but it serves to demonstrate what a user should be expected to work with. Users will want to interact directly with the list and its iterators in C++. You create a Node, and then add the Node to your list. That's a level of DIY that no user wants to be bothered with. In the code below, you'll see that a Node is still used, but it only exists within the List class. Users will never see a Node.
You can look in functions like push_back() (similar to your append) for specific answers related to your question.
To explain it a bit more, the pointers are key. Yes, I declare a local Node* that will go out of scope, but the object created continues to exist on the heap. And the list is able to keep track of these Nodes due to how linked lists work, namely that the Nodes know where their neighbors live (hold their addresses).
There is also a List<T>::iterator class. In the declaration, functions marked as // minimum are required if you want to use your linked list in a range-based for loop. The other functions do work toward satisfying the requirements of LegacyBidirectionalIterator; this is the level of iterator used by std::list in the C++ Standard Library.
The code below should only be considered a decent example (Hopefully not too presumptuous on my part). It is lacking some functionality that's found in std::list, and likely does a few things in non-optimal manners. A big thing that will need tweaking is removing the member function find() and make the class work with std::find().
list.hpp
#ifndef MY_LIST_HPP
#define MY_LIST_HPP
#include <algorithm> // std::swap
#include <cstddef> // std::size_t
/*
* Pre-declare template class and friends
*/
template <typename T>
class List;
template <typename T>
void swap(List<T>& lhs, List<T>& rhs);
/*
* List Class Declaration
*/
template <typename T>
class List {
public:
List() = default;
List(T val);
List(const List& other);
List(List&& other);
~List();
void push_front(T val);
void push_back(T val);
class iterator;
iterator begin();
iterator end();
iterator find(T val);
std::size_t size() const;
iterator erase(iterator toErase); // Implement
void clear();
bool operator=(List other);
friend void swap<T>(List& lhs, List& rhs);
private:
struct Node {
T data;
Node* prev = nullptr;
Node* next = nullptr;
Node(T val) : data(val) {}
};
Node* m_head = nullptr;
Node* m_tail = nullptr;
std::size_t m_size = 0;
// Helper functions
void make_first_node(T val);
Node* find_node(T val);
};
/*
* List Iterator Declaration
*/
template <typename T>
class List<T>::iterator {
public:
iterator() = default;
iterator(List<T>::Node* node); // minimum
T& operator*(); // minimum
iterator& operator++(); // minimum
iterator operator++(int);
iterator& operator--();
iterator operator--(int);
bool operator==(const iterator& other); // minimum
bool operator!=(const iterator& other); // minimum
private:
Node* m_pos = nullptr;
};
/*
* List Implementation
*/
template <typename T>
List<T>::List(T val) : m_head(new Node(val)), m_tail(m_head), m_size(1) {}
template <typename T>
List<T>::List(const List<T>& other) {
m_head = new Node((other.m_head)->data);
m_tail = m_head;
m_size = 1;
Node* walker = (other.m_head)->next;
while (walker) {
push_back(walker->data);
++m_size;
walker = walker->next;
}
}
template <typename T>
List<T>::List(List&& other) : List() {
swap(*this, other);
}
template <typename T>
List<T>::~List() {
clear();
}
template <typename T>
void List<T>::push_front(T val)
{
if (!m_head) {
make_first_node(val);
return;
}
Node* tmp = new Node(val);
tmp->next = m_head;
m_head->prev = tmp;
m_head = tmp;
++m_size;
}
template <typename T>
void List<T>::push_back(T val) {
if (!m_head) {
make_first_node(val);
return;
}
Node* tmp = new Node(val);
tmp->prev = m_tail;
m_tail->next = tmp;
m_tail = tmp;
++m_size;
}
template <typename T>
typename List<T>::iterator List<T>::begin() {
return iterator(m_head);
}
template <typename T>
typename List<T>::iterator List<T>::end() {
return iterator(nullptr);
}
template <typename T>
typename List<T>::iterator List<T>::find(T val) {
return iterator(find_node(val));
}
template <typename T>
std::size_t List<T>::size() const {
return m_size;
}
template <typename T>
typename List<T>::iterator List<T>::erase(typename List<T>::iterator toErase)
{
Node* node = find_node(*toErase);
if (node->prev) {
node->prev->next = node->next;
} else {
m_head = node->next;
}
if (node->next) {
node->next->prev = node->prev;
} else {
m_tail = node->prev;
}
Node* toReturn = node->next;
delete node;
return toReturn;
}
template <typename T>
void List<T>::clear() {
Node* tmp = m_head;
while (m_head) {
m_head = m_head->next;
delete tmp;
tmp = m_head;
}
m_tail = nullptr;
m_size = 0;
}
template <typename T>
bool List<T>::operator=(List other) {
swap(*this, other);
return *this;
}
template <typename T>
void List<T>::make_first_node(T val) {
m_head = new Node(val);
m_tail = m_head;
m_size = 1;
}
template <typename T>
typename List<T>::Node* List<T>::find_node(T val) {
if (!m_head) {
return nullptr;
}
Node* walker = m_head;
while (walker != nullptr && walker->data != val) {
walker = walker->next;
}
return walker;
}
template <typename T>
void swap(List<T>& lhs, List<T>& rhs) {
using std::swap;
swap(lhs.m_head, rhs.m_head);
swap(lhs.m_tail, rhs.m_tail);
swap(lhs.m_size, rhs.m_size);
}
/*
* List Iterator Implementation
*/
template <typename T>
List<T>::iterator::iterator(Node* node) : m_pos(node) {}
template <typename T>
T& List<T>::iterator::operator*() {
return m_pos->data;
}
template <typename T>
typename List<T>::iterator& List<T>::iterator::operator++() {
m_pos = m_pos->next;
return *this;
}
template <typename T>
typename List<T>::iterator List<T>::iterator::operator++(int) {
iterator tmp(m_pos);
++(*this);
return tmp;
}
template <typename T>
typename List<T>::iterator& List<T>::iterator::operator--() {
m_pos = m_pos->prev;
return *this;
}
template <typename T>
typename List<T>::iterator List<T>::iterator::operator--(int) {
iterator tmp(m_pos);
--(*this);
return tmp;
}
template <typename T>
bool List<T>::iterator::operator==(const iterator& other) {
return m_pos == other.m_pos;
}
template <typename T>
bool List<T>::iterator::operator!=(const iterator& other) {
return !(*this == other);
}
#endif
Code and explaination
I think this code should work as well, so there is no need to have two pointers. It's based on an example of “good taste” that Linus Torvalds gave in an interview.
void append(Node &element)
{
Node** cursor = &first_node.ptr;
while ((*cursor) != nullptr)
cursor = &(*cursor)->ptr;
*cursor = &element;
}
It eliminates the need for multiple pointers, it eliminates edge cases and it allows us to evaluate the condition of the while loop without having to let go of the pointer that points to the next element. This allows us to modify the pointer that points to NULL and to get away with a single iterator as opposed to ptr and prev_ptr.
Naming conventions
Also the norm is to call the first node in the linked list head and to call the pointer to the next node next instead of ptr, so I will rename them in the following code.
void append(Node &new)
{
Node** cursor = &head.next;
while ((*cursor) != nullptr)
cursor = &(*cursor)->next;
*cursor = &new;
}
I have a problem with creating base class for DoubleLinkedList.
Right now it's giving me this error
/tmp/cc3lORia.o:(.rodata._ZTV24AbstractDoubleLinkedListIiE[_ZTV24AbstractDoubleLinkedListIiE]+0x10):
undefined reference to
`AbstractDoubleLinkedList::createNewNode(int)' collect2: error:
ld returned 1 exit status
I've tried this and that as you can see by commented lines in code, but none of it works.
So how to define abstract template class with abstract method (factory method by the way) and then redefine it in children classes?
/*
* AbstractDoubleLinkedList.hpp
*
* Created on: Mar 2, 2015
* Author: michael
*/
#ifndef ABSTRACTDOUBLELINKEDLIST_H_
#define ABSTRACTDOUBLELINKEDLIST_H_
#include <vector>
using namespace std;
template <class T> class ListNode {
private:
void init();
public:
ListNode();
ListNode(T value);
ListNode *previous;
ListNode *next;
T value;
};
template <class T> void ListNode<T>::init() {
previous = nullptr;
next = nullptr;
}
template <class T> ListNode<T>::ListNode() {
init();
}
template <class T> ListNode<T>::ListNode(T value) {
init();
this->value = value;
}
template <class T> class AbstractDoubleLinkedList {
private:
void pullOutNode(ListNode<T> *node);
protected:
virtual ListNode<T>* createNewNode(T element);
public:
AbstractDoubleLinkedList();
void push_back(T element);
T front();
T back();
void insertBefore(ListNode<T> *node, ListNode<T> *beforeNode);
void insertAfter(ListNode<T> *node, ListNode<T> *afterNode);
void moveNodeAfter(ListNode<T> *node, ListNode<T> *afterNode);
vector<T> toVector();
ListNode<T> *frontNode;
ListNode<T> *backNode;
};
template <class T> void AbstractDoubleLinkedList<T>::push_back(T element) {
ListNode<T>* node = createNewNode(element);
node->previous = backNode;
if (backNode != nullptr) {
backNode->next = node;
node->previous = backNode;
}
else {
frontNode = node;
}
backNode = node;
}
template <class T> void AbstractDoubleLinkedList<T>::pullOutNode(ListNode<T> *node) {
if (node != frontNode) {
node->previous->next = node->next;
}
else {
frontNode = node->next;
}
if (node != backNode) {
node->next->previous = node->previous;
}
else {
backNode = node->previous;
}
}
template <class T> T AbstractDoubleLinkedList<T>::front() {
return frontNode->value;
}
template <class T> T AbstractDoubleLinkedList<T>::back() {
return backNode->value;
}
template <class T> void AbstractDoubleLinkedList<T>::insertAfter(ListNode<T> *node, ListNode<T> *afterNode) {
node->previous = afterNode;
node->next = afterNode->next;
afterNode->next = node;
if (afterNode == backNode) {
backNode = node;
}
}
template <class T> void AbstractDoubleLinkedList<T>::insertBefore(ListNode<T> *node, ListNode<T> *beforeNode) {
node->next = beforeNode;
beforeNode->previous->next = node;
beforeNode->previous = node;
if (beforeNode == frontNode) {
frontNode = node;
}
}
template <class T> void AbstractDoubleLinkedList<T>::moveNodeAfter(ListNode<T> *node, ListNode<T> *afterNode) {
pullOutNode(node);
node->previous = afterNode;
node->next = afterNode->next;
if (node->next == nullptr) {
backNode = node;
}
afterNode->next = node;
}
template <class T> vector<T> AbstractDoubleLinkedList<T>::toVector() {
ListNode<T>* node = frontNode;
vector<int> listAsVector;
bool shouldHaveAnother = (frontNode != nullptr);
while(shouldHaveAnother) {
listAsVector.push_back(node->value);
if (node->next != nullptr)
node = node->next;
else {
shouldHaveAnother = false;
}
}
return listAsVector;
}
template <class T> AbstractDoubleLinkedList<T>::AbstractDoubleLinkedList() {
frontNode = nullptr;
backNode = nullptr;
}
#endif /* ABSTRACTDOUBLELINKEDLIST_HPP_ */
/*
* DoubleLinkedList.hpp
*
* Created on: Feb 26, 2015
* Author: michael
*/
#ifndef DOUBLELINKEDLIST_HPP_
#define DOUBLELINKEDLIST_HPP_
#include "AbstractDoubleLinkedList.hpp"
template <class T> class DoubleLinkedList : public AbstractDoubleLinkedList<T> {
protected:
ListNode<T>* createNewNode(T element) {
return new ListNode<T>(element);
}
public:
~DoubleLinkedList() {
ListNode<T>* node = this->backNode;
bool shouldHaveAnother = (node != nullptr);
while(shouldHaveAnother) {
ListNode<T>* ptr = node->previous;
delete node;
if (ptr != nullptr)
node = ptr;
else {
shouldHaveAnother = false;
}
}
};
};
//template <class T> ListNode<T> DoubleLinkedList<T>::createNewNode(T element) {
// return new ListNode<T>(element);
//}
//template <class T> DoubleLinkedList<T>::~DoubleLinkedList() {
//
// ListNode<T>* node = this->backNode;
// bool shouldHaveAnother = (node != nullptr);
// while(shouldHaveAnother) {
// ListNode<T>* ptr = node->previous;
// delete node;
// if (ptr != nullptr)
// node = ptr;
// else {
// shouldHaveAnother = false;
// }
// }
//}
#endif /* DOUBLELINKEDLIST_HPP_ */
EDIT1:
So, there is a question "Why do I need my own container class and why not use list or vector?"
I need a data structure with constant time random access and constant time deletion and insertion. BUT I've already tried std::unordered_set and It's not good enough (while technically it meets the requirements) because of allocations and deallocations of memory (when deleting and inserting)
So I figured another way. I want to use a linked list with one "guard" element and the end. When I need to "delete" element from it I would move it after guard. And to check if list is "empty" I would check if the first element is the guard element. But I need a constant-time random access. This can be achieved by map of pointers to every element.
But to achieve maximum performance I need to also minimize cache misses. And I thinks that std::list would be scattered across the memory, because it is the normal behaviour for it. So I figured that the only way to do so - is to allocate vector> and then use this preallocated nodes to new elements.
So am I wrong somewhere? Can I achieve maximum performance more easier?
I'm been having trouble with either my constructor or my insert function, I'm not sure which is the issue, but the real issue is that I get the error code mentioning I'm having an error with things getting de-referenced. All I'm trying to do is insert a node containing an integer value in the correct position.
This is the declaration:
template <class T>
class LinkedList
{
template <class T>
struct Node
{
T mData;
Node<T> *mNext;
/* Pre: None
* Post: This object is initialized using default values
* Purpose: To initialize date object
*************************************************************************/
Node()
{
mData = T();
mNext = NULL;
}
/* Pre: None
* Post: This object is initialized using specified data
* Purpose: To intialize date object
*************************************************************************/
Node(T data, Node<T>* next)
{
mData = data;
mNext = next;
}
};
private:
Node<T> *mHead;
int mCount;
public:
LinkedList();
~LinkedList();
int getCount();
T getData(int index);
void setData(int index, T data);
void clear();
void display();
bool insert(T data);
bool isEmpty();
bool isExist(T searchKey);
bool remove(T searchKey);
T removeAt(int index);
T operator[](int index);
void operator=(LinkedList<T> *list);
};
And this is the insert:
template <class T>
bool LinkedList<T>::insert(T data)
{
Node<T>* current = mHead;
if (!current)
{
if (mCount == 0)
{
current->mData = data;
}
else
{
while (current->mNext != NULL)
{
if (current->mData == data)
return false;
else if (current->mNext->mData < data)
{
Node<T>* newNode = new Node<T>();
newNode->mData = data;
newNode->mNext = current->mNext;
current->mNext = newNode;
}
current = current->mNext;
}
}
}
else
{
Node<T>* node = new Node<T>(data, NULL);
current->mNext = node;
}
mCount++;
return true;
}
Every time my program breaks, it's at this line
current->mData = data;
You shouldn't declare Node class as template, in fact you should get a compile error message like error: declaration of 'T' shadows template parameter.
Remove template<class T> from Node and change Node<T> to Node throughout the code.
So I am trying to create my own implementation file which contains instructions for a Queue. I decided to use a linked list to implement the Queue class, meaning that I need to use my own Node struct. Unfortunately, I am stuck and don't know how to properly include this within the file.
This is what I have so far:
#include <string>
#ifndef NODE
template <class DataType>
struct Node
{
DataType data;
Node *next;
};
#endif
template <class DataType>
class Queue
{
public:
Queue();
bool isEmpty() const;
void push(const DataType& parameter);
bool peek(DataType& parameter) const;
bool pop(DataType& parameter);
void makeEmpty();
private:
Node<DataType>* front;
Node<DataType>* end;
};
template <class DataType>
Queue<DataType>::Queue()
: front(0), end(0)
{
}
template <class DataType>
bool Queue<DataType>::isEmpty() const {return 0 == front;}
template <class DataType>
void Queue<DataType>::push(const DataType& parameter)
{
Node<DataType>* node = new Node<DataType>;
node->data = parameter;
node->next = 0;
if (end) end->next = node;
else front = node;
end = node;
}
template <class DataType>
bool Queue<DataType>::peek(DataType& parameter) const
{
if (0 == front) return false; // failed
parameter = front->data;
return true; // success
}
template <class DataType>
bool Queue<DataType>::pop(DataType& parameter)
{
if (0 == front) return false; // failed
parameter = front->data;
Node<DataType>* p = front->next;
delete front;
front = p;
if (front == 0) end = 0;
return true; // success
}
template <class DataType>
void Queue<DataType>::makeEmpty()
{
end = 0;
Node<DataType>* p;
while (front)
{
p = front->next;
delete front;
front = p;
}
}
I'm not sure if I am enclosing the struct by the #ifndef correctly (i'm not even sure if this is the route I should be taking :/), should I be doing something similar to this or should I be doing something else with the code for the struct?
You can just drop the #ifdef/#endif entirely
This is a class template and it may occur many times in several tranlation units, as long as all the occurrences are identical (One Definition Rule)
Alternative
Since Node<> is purely a private concern, I'd make it a nested struct.
Here's a little demo making this more 'modern C++' style.
Edit Thanks to #R.MartinhoFernandes for showing a few more improvements and for reviewing this.
#include <memory>
template <typename T>
struct Queue {
Queue() : front(), end(/*nullptr*/) {}
// Copy-And-Swap idiom
// see http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap
// or http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom
void swap(Queue& q) noexcept {
using std::swap;
swap(q.front, front);
swap(q.end, end);
}
Queue(Queue const& q) : front(), end() {
for(auto it=q.front.get(); it; it=it->next.get())
push(it->data);
}
Queue& operator=(Queue q) {
std::swap(*this, q);
return *this;
}
// end Copy-and-swap
// prevent stack overflows in ~Node if the list grows large (say >1k elements)
~Queue() { clear(); }
bool isEmpty() const {
return !front;
}
void push(T const& data) {
Ptr node(new Node(data));
if (end)
end->next = std::move(node);
else
front = std::move(node);
end = node.get();
}
bool peek(T& data) const {
if(front) data = front->data;
return front.get();
}
bool pop(T& data) {
if(!front) return false;
data = front->data;
front = std::move(front->next);
if(!front) end = nullptr;
return true;
}
void clear() {
end = nullptr;
while(front) front = std::move(front->next);
}
private:
struct Node;
typedef std::unique_ptr<struct Node> Ptr;
struct Node {
Node(T data) : data(std::move(data)), next() {}
T data;
Ptr next;
};
Ptr front;
Node* end;
};
#include <iostream>
int main(int argc, const char *argv[]) {
Queue<int> test;
test.push(1);
test.push(2);
test.push(3);
test.push(5);
test.clear();
test.push(32028);
test.push(10842);
test.push(1839);
test.push(23493);
test.push(9857);
int x;
test.peek(x);
while(test.pop(x)) {
std::cout << x << '\n';
}
}
Note: Perhaps the code in push has been golfed a bit too far, but hey, it shows you how modern C++ requires much less handholding (even without std::make_unique).
Note how I think Clang correctly handles the following version (i.e. with implicit std::move):
void push(const DataType& parameter) {
end = ((end? end->next : front) = Ptr(new Node(parameter))).get();
}
I'm not quite sure why gcc rejects it.
I have been struggling for too long a time now with a rather simple question about how to create a generic linked list in c++. The list should be able contain several types of structs, but each list will only contain one type of struct. The problem arises when I want to implement the getNode() function [see below], because then I have to specify which of the structs it should return. I have tried to substitute the structs with classes, where the getNode function returns a base class that is inherited by all the other classes, but it still does not do the trick, since the compiler does not allow the getNode function to return anything but the base class then.
So here is some code snippet:
typedef struct struct1
{
int param1;
(...)
} struct1;
typedef struct struct2
{
double param1;
(...)
} struct2;
typedef struct node
{
struct1 data;
node* link;
} node;
class LinkedList
{
public:
node *first;
int nbrOfNodes;
LinkedList();
void addNode(struct1);
struct1 getNode();
bool isEmpty();
};
LinkedList::LinkedList()
{
first = NULL;
nbrOfNodes = 0;
}
void LinkedList::addNode(struct1 newData)
{
if (nbrOfNodes == 0)
{
first = new node;
first->data = newData;
}
else
{
node *it = first;
for (int i = 0; i < nbrOfNodes; i++)
{
it = it->link;
}
node *newNode = new node;
newNode->data = newData;
it->link = newNode;
}
nbrOfNodes++;
}
bool LinkedList::isEmpty()
{
return !nbrOfNodes;
}
struct1 LinkedList::getNode()
{
param1 returnData = first->data;
node* deleteNode = first;
nbrOfNodes--;
if (nbrOfNodes)
first = deleteNode->link;
delete deleteNode;
return returnData;
}
So the question, put in one sentence, is as follows: How do I adjust the above linked list class so that it can also be used for struct2, without having to create a new almost identical list class for struct2 objects? As I said above, each instance of LinkedList will only deal with either struct1 or struct2.
Grateful for hints or help
There is already a generic link list available in C++, std::list. It will definitely be more efficient & should suffice for your usage.
If you still want to create your own generic link list
You should consider using templates and create a template implmentation of link list.
In c, where templates are not available the data node is stored in the form of a void* pointer. It takes advantage of the fact that a void pointer can point to any generic data type, You might consider that approach as well.
Basic tempaltes are easy.
Just declare the class as template with a templated type variable.
Now everywhere you have the declare type which you want to be generic, in the class, replace the explicit type name with the templated variable name.
For example, in your code, you want struct1 to be generic, so we replace it with T:
template<class T>
class LinkedList {
public:
node *first;
int nbrOfNodes; LinkedList();
void addNode(T);
T getNode();
bool isEmpty();
};
The STL source would be a piece of code to study.
You could also try https://github.com/simonask/ftl/blob/master/list.hpp
Both use templates, which should be understood to be able to make any generic classes.
Here is a generic implementation without using STL. You can create a templated class with a generic type and instantiate the singly_linked_list.
namespace api
{
template <typename T>
class i_list
{
public:
i_list() = default;
virtual ~i_list(){}
/* Add to front*/
virtual int push_front(T t_value) = 0;
/* Return the front item*/
virtual T top_front() = 0;
/* Remove and return the front elenment */
virtual T pop_front() = 0;
/* Add to the back */
virtual int push_back(T t_value) = 0;
/* Returns the back item */
virtual T top_back() = 0;
/* Removes and returns the back item */
virtual T pop_back() = 0;
/* Is key in the list */
virtual bool find(T t_value) = 0;
/* Remove the key from the list */
virtual int erase(T t_value) = 0;
/* Erases the entire list */
virtual void erase_all() = 0;
/* Is the list empty */
virtual bool empty() = 0;
/* return the size of the list */
virtual size_t size() = 0;
};
}
namespace list
{
template<typename T>
struct node
{
T m_data;
node* m_next;
node* m_jump;
int m_order;
node(T t_data) : m_data(t_data), m_next(nullptr) , m_jump(nullptr), m_order(-1){}
};
template<typename T>
class singly_linked_list : public api::i_list<T>
{
public:
singly_linked_list() : m_head(nullptr), m_size(0){}
~singly_linked_list() {erase_all();}
virtual int push_front(T t_value) override;
virtual T top_front() override;
virtual T pop_front() override;
virtual int push_back(T t_value) override;
virtual T top_back() override;
virtual T pop_back() override;
virtual bool find(T t_value) override;
virtual int erase(T t_value) override;
virtual void erase_all() override;
virtual bool empty() override;
virtual size_t size() override;
template<typename U>
friend node<U>* get_head(singly_linked_list<U>& t_list);
private:
node<T>* get_last_node();
node<T>* get_node_until(size_t t_index);
node<T>* find_node(T t_value);
node<T>* get_node_pointer(size_t t_position);
int find_node_index(T t_value);
singly_linked_list<T> get_all_addresses();
node<T>* m_head = nullptr;
size_t m_size;
};
/* O(1) */
template<typename T>
inline int singly_linked_list<T>::push_front(T t_value)
{
node<T>* new_node = new node<T>(t_value);
new_node->m_next = m_head;
m_head = new_node;
m_size++;
return 0;
}
/* O(1) */
template<typename T>
inline T singly_linked_list<T>::top_front()
{
if (empty())
{
std::cout << " list is empty " << std::endl;
return T();
}
return m_head->m_data;
}
/* O(1) */
template<typename T>
inline T singly_linked_list<T>::pop_front()
{
if (empty())
{
std::cout << " list is empty " << std::endl;
return T();
}
/* Value to be returned */
T value = m_head->m_data;
node<T>* next_node = m_head->m_next;
delete(m_head);
m_head = next_node;
m_size--;
return value;
}
/* O(N) */
template<typename T>
inline int singly_linked_list<T>::push_back(T t_value)
{
node<T>* new_node = new node<T>(t_value);
if (empty())
{
m_head = new_node;
m_size++;
return 0;
}
get_last_node()->m_next = new_node;
m_size++;
return 0;
}
/* O(N) */
template<typename T>
inline T singly_linked_list<T>::top_back()
{
if (empty())
{
std::cout << " list is empty " << std::endl;
return T();
}
return get_last_node()->m_data;
}
/* O(N) */
template<typename T>
inline T singly_linked_list<T>::pop_back()
{
T value;
if (empty())
{
std::cout << " list is empty " << std::endl;
return T();
}
if (size() == 1)
{
value = m_head->m_data;
delete(m_head);
m_head = nullptr;
m_size = 0;
return value;
}
node<T>* last_node = get_last_node();
node<T>* last_node_before = get_node_until(size() -1);
value = last_node->m_data;
delete(last_node);
last_node_before->m_next = nullptr;
m_size--;
return value;
}
/* O(N) - Worst case */
template<typename T>
inline bool singly_linked_list<T>::find(T t_value)
{
return find_node(t_value) != nullptr;
}
/* O(N) - Worst case */
template<typename T>
inline int singly_linked_list<T>::erase(T t_value)
{
/* Node with t_value deos not exists */
if (empty())
return -1;
if (size() == 1)
{
delete(m_head);
m_head = nullptr;
m_size = 0;
return 0;
}
int index = find_node_index(t_value);
if (index == -1)
return index;
node<T>* node_to_erase = get_node_until(index + 1);
node<T>* before_node_to_erase = get_node_until(index);
node<T>* after_node_to_erase = get_node_until(index + 2);
before_node_to_erase->m_next = after_node_to_erase;
delete(node_to_erase);
m_size--;
return 0;
}
/* O(N) - Worst case */
template<typename T>
inline void singly_linked_list<T>::erase_all()
{
while(m_head != nullptr)
{
node<T>* next_node = m_head->m_next;
delete(m_head);
m_size--;
m_head = next_node;
}
}
/* O(1) */
template<typename T>
inline bool singly_linked_list<T>::empty()
{
return (m_head == nullptr);
}
/* O(1) */
template<typename T>
inline size_t singly_linked_list<T>::size()
{
return m_size;
}
template<typename T>
inline node<T>* singly_linked_list<T>::get_last_node()
{
node<T>* start_node = m_head;
node<T>* last_node = nullptr;
/* Traverse until the end */
while (start_node != nullptr)
{
last_node = start_node;
start_node = start_node->m_next;
}
return last_node;
}
template<typename T>
inline node<T>* singly_linked_list<T>::get_node_until(size_t t_index)
{
node<T>* start_node = m_head;
node<T>* until_node = nullptr;
/* Traverse until the a node before last node */
size_t index(1);
while (start_node != nullptr)
{
until_node = start_node;
if (index == t_index)
{
return until_node;
}
start_node = start_node->m_next;
index++;
}
return nullptr;
}
template<typename T>
inline node<T>* singly_linked_list<T>::find_node(T t_value)
{
node<T>* start_node = m_head;
/* Traverse until the end */
while (start_node != nullptr)
{
if (t_value == start_node->m_data)
return start_node;
start_node = start_node->m_next;
}
return nullptr;
}
template<typename T>
inline int singly_linked_list<T>::find_node_index(T t_value)
{
node<T>* start_node = m_head;
/* Traverse until the end */
int index(0);
while (start_node != nullptr)
{
if (t_value == start_node->m_data)
return index;
start_node = start_node->m_next;
index++;
}
/* t_value not found*/
return -1;
}
/* Returns the address of the specified node position
form the linke list chain */
template<typename T>
node<T>* singly_linked_list<T>::get_node_pointer(size_t t_position)
{
auto start_node{m_head};
/* Traverse until the end */
size_t i(0);
while (start_node != nullptr)
{
if(i == t_position)
return start_node;
start_node = start_node->m_next;
i++;
}
return nullptr;
}
template<typename U>
node<U>* get_head(singly_linked_list<U>& t_list)
{
return t_list.m_head;
}
}
struct1 and struct2 have different size in bytes, so sizeof(struct1) != sizeof(struct2). Returning the struct from function requires copying it, so c++ requires you to specify the correct type for it so that correct amount of bytes can be copied. To start correct this problem you need to think in the very low level:
struct GenericStruct {
void *ptr;
size_t size;
type_info t;
};
struct1 extract_struct1(GenericStruct &s);
struct2 extract_struct2(GenericStruct &s)
{
if (s.size != sizeof(struct2)) throw -1;
if (s.t != typeid(struct2)) throw -1;
struct2 *s2 = (struct2*)s.ptr;
return *s2;
}
GenericStruct make_generic(const struct1 &ss)
{
GenericStruct s;
s.ptr = (void*)&ss;
s.size = sizeof(struct1);
s.t = typeid(struct1);
return s;
}
GenericStruct make_generic(const struct2 &ss);
the real problem is that these functions can fail on runtime, if the sizes or types do not match. The copying is obviously also needed:
GenericStruct Copy(const GenericStruct &s);
After these basic primitives exists, you can create a class which has copy constructor and assignment operator which uses these functions to implement proper generic struct support.