Okay, So I've been working on some book examples and stuff and found this exercise to implement the STL List lookalike. I've made it somehow and it kinda works, but I've got some major flaws in the implementation. The biggest one is that I have totally no idea how to make my List.end() iterator to work as it's supposed to do.
I guess I'll show the code first and try to tell some of my ideas next.
#ifndef TESTS_LST_H
#define TESTS_LST_H
#include <memory>
#include <cstddef>
template<class T> class Node;
template<class T> class ListIter;
template<class T>
class List {
public:
typedef ListIter<T> iterator;
typedef const ListIter<T> const_iterator;
typedef std::size_t size_type;
List(): first(0), last(0), sz(0) {}
List(const List<T>& lst);
~List() { clear(); }
iterator begin() { return iterator(first); }
iterator end() { return iterator(last); }
iterator insert() {}
iterator erase() {}
const_iterator begin() const { return iterator(first); }
const_iterator end() const { return iterator(last); }
void push_back(const T& val);
void push_front(const T& val);
void clear();
void pop_front();
void pop_back();
size_type size() { return sz; }
bool empty() { return sz == 0; }
List& operator=(const List& l);
private:
Node<T>* first;
Node<T>* last;
size_type sz;
std::allocator<Node<T>>* alloc;
};
template<class T>
class Node {
public:
Node(): next(0), prev(0), value(0) {}
Node(const T& val): next(0), prev(0), value(val) {}
private:
Node<T>* next;
Node<T>* prev;
T value;
friend class List<T>;
friend class ListIter<T>;
};
template<class T>
class ListIter {
public:
typedef ListIter<T> iterator;
ListIter(Node<T>* iter): current_node(iter) {}
ListIter(): current_node(0) {}
ListIter(ListIter<T>* iter): current_node(iter->current_node) {}
inline T& operator*() { return current_node->value; }
iterator& operator=(const iterator& rhs) { *this->current_node = rhs.current_node; }
bool operator==(const iterator& rhs) { return current_node->value == rhs.current_node->value; }
bool operator!=(const iterator& rhs) { return current_node->value != rhs.current_node->value; }
iterator& operator++();
iterator operator++(int);
iterator& operator--();
iterator operator--(int);
private:
Node<T>* current_node;
friend class List<T>;
};
template<class T>
void List<T>::push_back(const T& val)
{
Node<T>* temp = alloc->allocate(1);
alloc->construct(temp, val);
if (first == 0) {
first = last = temp;
} else {
temp->prev = last;
last->next = temp;
last = temp;
}
sz++;
}
template<class T>
void List<T>::push_front(const T &val)
{
Node<T>* temp = alloc->allocate(1);
alloc->construct(temp, val);
if (first == 0) {
first = last = temp;
} else {
temp->prev = 0;
temp->next = first;
first->prev = temp;
first = temp;
}
sz++;
}
template<class T>
void List<T>::clear()
{
Node<T>* current = first;
while (current != 0) {
Node<T>* next = current->next;
//delete current
alloc->deallocate(current, 1);
alloc->destroy(current);
current = next;
}
first = last = 0;
sz = 0;
}
template<class T>
List<T>::List(const List &lst)
{
first = last = 0;
sz = 0;
for (auto it = lst.begin(); it != lst.end(); it++) {
push_back(it.current_node->value);
}
push_back(lst.last->value);
}
template<class T>
List<T>& List<T>::operator=(const List &lst)
{
first = last = 0;
sz = 0;
for (auto it = lst.begin(); it != lst.end(); ++it) {
push_back(it.current_node->value);
}
push_back(lst.last->value);
return *this;
}
template<class T>
void List<T>::pop_front()
{
first = first->next;
alloc->deallocate(first->prev, 1);
alloc->destroy(first->prev);
first->prev = 0;
sz--;
}
template<class T>
void List<T>::pop_back()
{
last = last->prev;
alloc->deallocate(last->next, 1);
alloc->destroy(last->next);
last->next = 0;
sz--;
}
template<class T>
ListIter<T>& ListIter<T>::operator++()
{
current_node = current_node->next;
return *this;
}
template<class T>
ListIter<T>& ListIter<T>::operator--()
{
current_node = current_node->prev;
return *this;
}
template<class T>
ListIter<T> ListIter<T>::operator++(int)
{
iterator tmp(*this);
++*this;
return tmp;
}
template<class T>
ListIter<T> ListIter<T>::operator--(int)
{
iterator tmp(*this);
--*this;
return tmp;
}
#endif //TESTS_LST_H
As you can see .end() function returns a regular last element of the list and not the one past the end as it should. Should I try to rework this part to possibly keep the *last as the one past the end iterator and use the operator+ to iterate through the list to omit the need in the pointer to the end of the actual list?
Something like this (not sure about the corectness of the code below):
iterator& operator+(std::size_type n)
{
for (auto i = 0; i < n; ++i) {
++*this;
}
return *this;
}
But I'm not sure that's how the stuff works in the actual implementation, loops could be very demanding after all.
I know that this stuff is already out there and works and all that. That's just for the educational purposes, so I hope to hear some ideas. Thanks in advance.
Iterator were known in the past as "smart pointer", since they works like that. (Indeed, pointers are iterators, but not the opposed). So, think an iterator like a pointer.
"One past the end" is clear what means when you are working with vectors: a vector contains its elements in contiguous space. Indeed, it is possible to implement vector iterator with just pointers. But that is not the case for a linked list, where generally its element are not in contiguous memory.
Because you implemented the List class as a doubled linked list, I suggest you to change the first and last pointers by a head:
template<class T>
class List {
// ...
private:
Node<T> head;
size_type sz;
};
So, the begin() iterator become head.next and end() iterator become &head. This works as far the last element in the list points to the head.
BTW: You don't need to create Node<T> as a class with friends classes. It is just an implementation detail. Change it to a struct and put it in a implementation namespace.
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 days ago.
Improve this question
I have made this doubly linked list class in C++ , it utilises sentinel nodes as well in it , I was making an insert(iterator it , T& value) function in the list , which inserts a new node before the node that is specified by the iterator it . I was testing the code , it wasn't working at the end cases i.e when the iterator had its value as the starting node and the ending node
Doubly Linked List class
template <typename T>
class DoubleList
{
struct Node
{
Node *prev;
Node *next;
T data;
Node(const T &data = T{}, Node *next = nullptr, Node *prev = nullptr)
{
this->data = data;
this->next = next;
this->prev = prev;
}
};
Node *front;
Node *back;
int size;
public:
class const_iterator
{
public:
const_iterator() {}
const_iterator &operator++() {}
const_iterator operator++(int) {}
const_iterator &operator--() {}
const_iterator operator--(int) {}
bool operator==(const_iterator rhs) {}
bool operator!=(const_iterator rhs) {}
const T &operator*() const {}
};
class iterator : public const_iterator
{
public:
iterator() {}
iterator &operator++() {}
iterator operator++(int) {}
iterator &operator--() {}
iterator operator--(int) {}
T &operator*() {}
const T &operator*() const {}
};
iterator begin()
{
return iterator(this->front->next, this);
}
iterator end()
{
return iterator(this->back, this);
}
const_iterator cbegin() const
{
return const_iterator(this->front->next, this);
}
const_iterator cend() const
{
return const_iterator(this->back, this);
}
Constructor for the linked list
template <typename T>
DoubleList<T>::DoubleList()
{
front = new Node();
back = new Node();
front->next = back;
back->prev = front;
}
template <typename T>
DoubleList<T>::~DoubleList()
{
Node *curr = this->front;
while (this->curr != back)
{
Node *next = curr->next;
delete curr;
curr = next;
}
delete this->front;
delete this->back;
this->size = 0;
}
iterator function
class iterator : public const_iterator
{
friend class DoubleList;
iterator(Node *n, const DoubleList *linkedList)
{
const_iterator(n, linkedList);
}
public:
iterator()
{
const_iterator();
}
iterator &operator++()
{
if (this->curr)
{
this->curr = this->curr->next;
}
return *this;
}
iterator operator++(int)
{
iterator old = *this;
if (this->curr)
{
this->curr = this->curr->next;
}
return old;
}
iterator &operator--()
{
if (this->curr)
{
this->curr = this->curr->prev;
}
else
{
this->curr = this->list->back;
}
return *this;
}
iterator operator--(int)
{
iterator old = *this;
if (this->curr)
{
this->curr = this->curr->prev;
}
else
{
this->curr = this->list->back;
}
return old;
}
T &operator*()
{
return this->curr->data;
}
const T &operator*() const
{
return this->curr->data;
}
Insert() function
template <typename T>
typename DoubleList<T>::iterator DoubleList<T>::insert(iterator it, const T &data)
{
Node *nn;
if (front->next != back)
{
nn = new Node(data, it.curr, it.curr->prev);
it.curr->prev->next = nn;
it.curr->prev = nn;
}
else
{
nn = new Node(data, back, front);
front->next = nn;
back->prev = nn;
}
this->size_++;
return iterator(nn, this);
}
the error occurs when i create a new list and insert a node into it
when i check wether end() and begin() point to different nodes in the linkedList ,it points to the same node , meaning that the node was never added into the list , what can i correct here to make it work fine?
a minimal reproducible example can be done by the following code
int main(){
DoubleList<int> theList;
DoubleList<int>::iterator it1 = theList.insert(theList1.begin(), 1);
if (theList.begin() == theList.end())
{
std::cout<<"Error recieved"<<std::endl;
}
}
template <typename T>
class LinkedList {
struct node;
class Iterator;
public:
LinkedList() {}
LinkedList(std::initializer_list<T> init_list) {
this->operator=(init_list);
}
template <typename InputIterator>
LinkedList(InputIterator first, InputIterator last) {
for (; first != last; ++first)
this->push_back(*first);
}
LinkedList(const LinkedList& another) {
this->operator=(another);
}
~LinkedList() {
while (this->head) {
node* old_head = this->head;
this->head = old_head->next;
delete old_head;
}
}
Iterator begin() {
return Iterator(this->head);
}
Iterator end() {
return Iterator(this->tail->next);
}
I tried to add an empty node at the tail->next, however I can't get the result that I want. And without the empty node I just get a segmentation fault when I run the code.
class Iterator {
friend class LinkedList;
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = int;
using pointer = T*;
using reference = T&;
Iterator(node* ptr) : ptr(ptr) {}
Iterator(const Iterator& other) {
this->operator=(other);
}
Iterator& operator=(const Iterator& that) {
this->ptr = that.ptr;
return *this;
}
Iterator& operator++() {
this->ptr = ptr->next;
return *this;
}
Iterator operator++(int) {
Iterator tmp(*this);
this->operator++();
return tmp;
}
Iterator& operator--() {
this->ptr = ptr->prev;
return *this;
}
Iterator operator--(int) {
Iterator tmp(*this);
this->operator--();
return tmp;
}
bool operator!=(Iterator that) const { return !(this->operator==(that)); }
bool operator==(Iterator that) const { return this->ptr == that.ptr; }
T& operator*() const { return ptr->data; }
Iterator* operator->() { return this; }
private:
node* ptr = nullptr;
};
Here is the main function I used to test and when I print the stl_list end() it prints the size of the list. I'm confused what should be returned for this function. I thought it was supposed to be an empty nullptr that points to the location after the tail.
int main() {
std::list<int> stl_list{1, 2, 3, 4, 5};
cs19::LinkedList<int> our_list{1, 2, 3, 4, 5};
std::cout << *stl_list.begin() << '\n';
std::cout << *our_list.begin() << '\n';
std::cout << *our_list.end() << '\n';
std::cout << *stl_list.end() << '\n';
}
Confused on how to implement the end function for doubly linked list
You could create a special node type that is one past the end node which only has a prev pointer.
template <typename T>
class LinkedList {
struct empty_node { // used for end iterator
empty_node* prev = nullptr;
};
struct node : empty_node {
empty_node* next;
T data;
};
Your LinkedList would then have an instance of empty_node whos only purpose is to let prev point back to the last real node. You'd then instantiate the end() iterator with a pointer to this empty_node.
You'd then use empty_node* everywhere until stepping the iterator forward or dereferencing an iterator.
Example with comments to explain:
template <typename T>
class LinkedList {
// node definitions as above
public:
class Iterator {
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = int;
using pointer = T*;
using reference = T&;
Iterator(empty_node* ptr) : ptr(ptr) {}
Iterator(const Iterator& other) : ptr(other.ptr) {}
Iterator& operator=(const Iterator& that) {
ptr = that.ptr;
return *this;
}
Iterator& operator++() {
// If the user steps forward, the iterator can't be at the end
// or the program will have undefined behavior as per the usual
// contract, so a cast is fine:
ptr = static_cast<node*>(ptr)->next;
return *this;
}
Iterator operator++(int) {
Iterator tmp(*this);
++*this;
return tmp;
}
Iterator& operator--() {
ptr = ptr->prev;
return *this;
}
Iterator operator--(int) {
Iterator tmp(*this);
--*this;
return tmp;
}
bool operator==(Iterator that) const { return this->ptr == that.ptr; }
bool operator!=(Iterator that) const {
return !(*this == that);
}
// Dereferencing is not allowed if the iterator is at the end so
// cast is fine:
reference operator*() const { return static_cast<node*>(ptr)->data; }
pointer operator->() { return &static_cast<node*>(ptr)->data; }
private:
empty_node* ptr = nullptr;
};
LinkedList() = default;
template <typename InputIterator>
LinkedList(InputIterator first, InputIterator last) {
for (; first != last; ++first) this->push_back(*first);
}
// Delegate to ctor taking iterators:
LinkedList(std::initializer_list<T> init_list)
: LinkedList(init_list.begin(), init_list.end()) {}
// Copy ctor - delegate to ctor taking iterators too:
LinkedList(const LinkedList& another)
: LinkedList(another.begin(), another.end()) {}
~LinkedList() {
// As long as it's not pointing at end, cast is fine:
for(empty_node* next; head != &beyond_end; head = next) {
next = static_cast<node*>(head)->next;
delete static_cast<node*>(head);
}
}
void push_back(const T& value) {
// Create a new node where `prev` points at the current last real node
// and `next` points at our empty end node:
node* nn = new node{{beyond_end.prev}, &beyond_end, value};
// link it:
if (head != &beyond_end) { // not the first node added
// the previous node must be a real node, so cast is fine:
static_cast<node*>(beyond_end.prev)->next = nn;
} else { // the first node added
head = nn;
}
beyond_end.prev = nn; // link beyond_end to the last real node
}
Iterator begin() { return Iterator(head); }
Iterator end() { return Iterator(&beyond_end); } // use `beyond_end` for end()
private:
empty_node* head = &beyond_end; // start pointing at the empty node
empty_node beyond_end; // note, not a pointer
};
So, instead of a node* tail; you'll have an instance of an empty_node in your LinkedList. It will have the same size as a node* so it doesn't waste space.
Demo
You could also store both the head and tail pointer in an empty_node to remove all casts except when dereferencing/deleteing.
template <typename T>
class LinkedList {
struct empty_node { // used for end iterator
empty_node* prev = nullptr;
empty_node* next = nullptr;
};
struct node : empty_node {
T data;
}
It requires minor changes to the example:
Demo
Consider a standard implementation of class Link of LinkedList in c++.
I want to know if its a good idea to overload the operator ++ in this class (I noticed that I repeat the line link = link->next; a lot when dealing with linked lists, so I thought it would be easier if I overload this operator). Here's my implementation:
#ifndef LINK_H
#define LINK_H
#include <iostream>
#include "typeinfo.h"
#include "LinkedList.h"
template <class T> class LinkedList; //|Forward declaration of the generic LinkedList.
template <class T>
class Link
{
public:
//|-------------------- Constructors --------------------
Link(T data): m_data(data), next(NULL){}
//|-------------------- Methods --------------------
T getData(){
return m_data;
}
T& getNext(){
return next;
}
void setNext(Link* newLink){
next = newLink;
}
void setData(T data){
m_data = data;
}
//|-------------------- Operator overload --------------------
bool operator==(Link& other){
if(this->m_data == other.m_data)
return true;
return false;
}
void operator++(){ //Is this okay?
this = this->next;
}
//|-------------------- Friend functions --------------------
friend std::ostream& operator<<(std::ostream& out,const Link<T>& link){
out<<link.m_data;
return out;
}
//|-------------------- Destructor --------------------
virtual ~Link(){}
protected:
public:
//|-------------------- Private fields --------------------
T m_data;
Link<T>* next;
friend class LinkedList<T>;
};
#endif // LINK_H
I guess the way that I tried to do it is not good (it does work as I expected). I tried to use this because I want it to work on pointer that is pointing to a certain link.
So, is it a good idea? if it is, what is the right way to implement it?
Thanks.
Maybe you should refactor your design and the code.
The link, or better said the Node, is normally implemented as an own class. An this class is embedded in the LinkedList class. And that Node class should be completely encapsulated and not shown to the outside world.
The user of the class will just deal with the data value. All the pointer stuff should be hidden.
And to be able to iterate over your class, which is similar to a std::forward_list, you can add ultra simple iterator functionality. Most functions can be implemented using a one liner.
I will show you below an ultra simple implementation example. This may be extended easily according to your needs.
From that you may take over some ideas to improve your design.
With the added iterator functionality, you may use range based for loops and std:: algorithms and all the like.
Please have a look and ask questions, if there should be something unclear.
#include <iostream>
#include <iterator>
#include <initializer_list>
#include <algorithm>
// Very simple implementation of a forward list
template <class T>
class SinglyLinkedList {
// The node
struct Node {
T data{}; // Data. Would normally be a templated argument
Node* next{}; // And the pointer to the next node
Node(const T& i, Node* n = nullptr) : data(i), next(n) {}; // Simple constructor to set a value and next pointer
};
Node* head{}; // This is the start of the list
// It would be advisable to have a tail pointer. We use the more inefficient approach here
Node* getLast() const { Node* n{ head }; while (n and n->next) n = n->next; return n; }
public:
// Constructor / Destructor --------------------------------------------------------------------------------------------------------
~SinglyLinkedList() { clear(); }
// Default constuctor
SinglyLinkedList() {} // Default
// From an initialization list
SinglyLinkedList(const std::initializer_list<T>& il) { clear(); for (const T& i : il) push_back(i); } // From initializer list
// Copy constructor
SinglyLinkedList(const SinglyLinkedList& other) { clear(); for (const T &i : other) push_back(i); }
// Move constructor. Will steal the elements from the other
SinglyLinkedList(SinglyLinkedList&& other) noexcept { head = other.head; other.head = nullptr; }
// Assignment operator
SinglyLinkedList& operator = (const SinglyLinkedList& other) { clear(); for (const T &i : other) push_back(i); }
// Move assignment operator
SinglyLinkedList& operator = (SinglyLinkedList&& other) { head = other.head; other.head = nullptr; }
// Housekeeping --------------------------------------------------------------------------------------------------------------
void clear() { Node* tmp{ head }; while (tmp) { Node* toDelete{ tmp }; tmp = tmp->next; delete toDelete; } head = nullptr; }
int empty() const { return head == nullptr; }
int size() const { int k{}; Node* n{ head }; while (n) { ++k; n = n->next; } return k; }
// Modify content --------------------------------------------------------------------------------------------------------------
void push_front(const T& i) { Node* n = new Node(i); n->next = head; head = n; };
void push_back(const T& i) { Node* n = new Node(i); Node* l = getLast(); if (l) l->next = n; else head = n; }
void pop_front() { if (head) { Node* tmp = head->next; delete head; head = tmp; } }
void pop_back() { // This is a little bit more difficult in a singly linked list
if (head) {
Node* n{ head }, * previous{};
while (n and n->next) {
previous = n;
n = n->next;
}
delete n;
if (previous)
previous->next = nullptr;
else
head->next = nullptr;
}
}
// Access elements --------------------------------------------------------------------------------
T front() const { return head ? head->data : 0; };
T back() const { Node* n = getLast(); return n ? n->data : 0; }
// Add iterator properties to class ---------------------------------------------------------------
struct iterator { // Local class for iterator
Node* iter{}; // Iterator is basically a pointer to the node
// Define alias names necessary for the iterator functionality
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
// Constructor
iterator() {}
iterator(Node* n) : iter(n) {}
// Dereferencing
reference operator *() const { return iter->data; }
pointer operator ->() const { return &iter->data; }
// Aithmetic operations
iterator& operator ++() { if (iter) iter = iter->next; return *this; }
iterator operator ++(int) { iterator temp{ *this }; ++* this; return temp; }
iterator operator +(const difference_type& n) const { iterator temp{ *this }; difference_type k{ n }; while (k--)++temp; return temp; }
iterator& operator +=(const difference_type& n) { difference_type k{ n }; while (k--)++* this; return *this; };
// Comparison
bool operator != (const iterator& other) const { return iter != other.iter; }
bool operator == (const iterator& other) const { return iter == other.iter; }
bool operator < (const iterator& other) const { return iter < other.iter; }
bool operator > (const iterator& other) const { return iter > other.iter; }
bool operator <= (const iterator& other) const { return iter <= other.iter; }
bool operator >= (const iterator& other) const { return iter >= other.iter; }
// Difference. Also complicated, because no random access
difference_type operator-(const iterator& other) const {
difference_type result{};
Node* n{ iter };
while (n and n != other.iter) {
++result;
n = n->next;
}
return result;
}
};
// Begin and end function to initialize an iterator
iterator begin() const { return iterator(head); }
iterator end() const { return iterator(); }
// Functions typcical for forward lists ----------------------------------------------------------------------
// Easy, becuase we can operate form the current iterator and do not need the "previous" element
iterator insertAfter(iterator& pos, const T& i) {
iterator result{};
if (pos.iter and pos.iter->next) {
Node* n = new Node(i, pos.iter->next);
pos.iter->next = n;
result = n;
}
return result;
}
iterator eraseAfter(iterator& pos) {
iterator result{};
if (pos.iter and pos.iter->next) {
Node* tmp = pos.iter->next->next;
delete pos.iter->next;
pos.iter->next = tmp;
result = pos.iter->next;
}
return result;
}
};
// Test/Driver Code
int main() {
// Example for initilizer list
SinglyLinkedList<int> sllbase{ 5,6,7,8,9,10,11,12,13,14,15 };
// Show move constructor
SinglyLinkedList<int> sll(std::move(sllbase));
// Add some values in the front
sll.push_front(4);
sll.push_front(3);
sll.push_front(2);
sll.push_front(1);
// Delete 1st element (Number 1)
sll.pop_front();
// Delete last element
sll.pop_back();
// Use a std::algorithm on our custom linked list. Works because we have an interator
SinglyLinkedList<int>::iterator iter = std::find(sll.begin(), sll.end(), 8);
// Now add an element after 8
iter = sll.insertAfter(iter, 88);
// End delete the 9
iter = sll.eraseAfter(iter);
// Use range based for loop. Works because, we have iterators
for (int i : sll)
std::cout << i << ' ';
}
I've created a Linked List in C++ and want to implement an iterator for it so that I can do range loops: for (const int& i : list) where Linked_List<int> list;.
My idea is to create the Iterator as part of the Linked_List class like this:
This is what I got so far:
template <typename T>
class Linked_List
{
public:
struct Iterator;
struct Node;
public:
Linked_List();
~Linked_List() noexcept(false);
Linked_List(const Linked_List&) = delete;
Linked_List(Linked_List&&) = delete;
Linked_List& operator=(const Linked_List&) = delete;
Linked_List& operator=(Linked_List&&) = delete;
void push_back(T);
void push_front(T);
void pop_back();
void pop_front();
bool empty() const;
T back() const;
T front() const;
//void swap(T, T);
//void insert(Iterator, T);
//void erase(Iterator);
//Iterator begin() const;
//Iterator end() const;
private:
Node* head;
Node* tail;
};
template<typename T>
struct Linked_List<T>::Node
{
Node() : prev(nullptr), next(nullptr) {}
Node(T t) : value(t), prev(nullptr), next(nullptr) {}
Node* prev;
Node* next;
T value;
};
Is this a good approach?
Should I do error checking when incrementing the list to check if current->next == tail? If so, how do I do that? Because my Iterator doesn't have a list object with a tail.
Edit:
I'm not sure how to implement the struct Iterator;, I get stuck when figuring out how to connect it with the list so that I can check if the current node returned from the iterator equals the tail in the list, in the Linked_List Iterator end() const method.
Let's say I've implemented all the necessary operators for an iterator like this:
struct Iterator
{
T& operator*() const { return current->value; }
bool operator!=(const Iterator& rhs) { return (*_current != rhs._current); }
Iterator& operator++()
{
current = current->next;
return *this;
}
};
How would I go about implementing Iterator Linked_List<T>::begin() const; and end() now?
I imagine an imaginary user making an iterator object like this:
Linked_List<int>::Iterator it;
An idea is to have a public constructor with no parameters and a private constructor that takes a node as a parameter which _current will be set to, and have the Linked_List class as a friend.
A few notes.
There are two options where to declare Node and Iterator. Inside the list class as List<T>::Node or outside as Node<T>. It is, in part, a matter of taste. From engineering perspective though, the symbol names are longer for nested classes, so your debuginfo is bigger. Also, when nested classes are also templates it is harder to specialize them if/when necessary (because that requires fully specializing the enclosing template first), but this is not the case here.
It leads to more elegant code when one list node is used as list head and tail. Empty list is a node whose next and prev point to itself. push_front appends to list.next which points to the first node or itself. push_back appends a node to list.prev which points to the last node or itself. When inserting/removing nodes there is no need to have special handling of the first and last nodes. E.g. :
struct Node {
Node *next_, *prev_;
Node()
: next_(this), prev_(this)
{}
~Node() {
unlink();
}
void push_back(Node* n) {
n->next_ = this;
n->prev_ = prev_;
prev_->next_ = n;
prev_ = n;
}
void unlink() {
Node *next = next_, *prev = prev_;
next->prev_ = prev;
prev->next_ = next;
next_ = this;
prev_ = this;
}
};
In the above, Node only needs two operations to be able to maintain a list. More than that, Node is itself a minimalist list that can be used for intrusive lists (with auto-unlink in the destructor). Note how using this makes checks for nullptr unnecessary - Node is always a valid list.
Error checking should be in debug mode only (use assert, for example). Otherwise, those checks penalise correct applications with unnecessary run-time checks.
Here is a minimal working example based on the ideas for you:
template<class T>
class List;
class Iterator;
class Node {
friend class Iterator;
template<class T> friend class List;
protected:
Node *next_, *prev_;
void push_back(Node* n) {
n->next_ = this;
n->prev_ = prev_;
prev_->next_ = n;
prev_ = n;
}
void unlink() {
Node *next = next_, *prev = prev_;
next->prev_ = prev;
prev->next_ = next;
next_ = this;
prev_ = this;
}
public:
Node()
: next_(this), prev_(this)
{}
~Node() { unlink(); }
};
class Iterator {
protected:
Node* node_;
Iterator(Node* node)
: node_(node)
{}
public:
Iterator& operator++() {
node_ = node_->next_;
return *this;
}
bool operator==(Iterator b) const { return node_ == b.node_; }
bool operator!=(Iterator b) const { return node_ != b.node_; }
// Implement the rest of iterator interface.
};
template<class T>
class List {
class NodeT : public Node {
friend class List<T>;
T value_;
NodeT(T t) : value_(t) {}
};
template<class U>
class IteratorT : public Iterator {
friend class List<T>;
NodeT* node() const { return static_cast<NodeT*>(node_); }
public:
U& operator*() const { return node()->value_; }
U* operator->() const { return &node()->value_; }
operator IteratorT<U const>() const { return node_; } // iterator to const_iterator conversion
IteratorT(Node* node) : Iterator{node} {}
};
Node list_;
public:
using iterator = IteratorT<T>;
using const_iterator = IteratorT<T const>;
~List() { clear(); }
bool empty() const { return list_.next_ == &list_; }
iterator begin() { return list_.next_; }
iterator end() { return &list_; }
void push_back(T t) { list_.push_back(new NodeT(t)); }
void erase(const_iterator i) { delete i.node(); }
void clear() {
while(!empty())
erase(begin());
}
// Implement the rest of the functionality.
};
int main() {
List<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
for(auto elem : l)
std::cout << elem << ' ';
std::cout << '\n';
}
I am looking for what's wrong in this custom double linkedlist homework assignment. It is not finished because I haven't sorted out the errors. I went through this code line by line with my tutor and he says it's correct, yet it displays errors.
We've tried viewing the whitespace, changing public to private + vice versa, adding/removing semicolons throughout the code, checking the text encoding. I don't need help with the actual linkedlist itself, just why I'm getting all these errors.
We must use nested class and must all be in one file The errors I am getting vary from 0 - 40 in number.
#pragma once
#include <initializer_list>
#include <iostream>
#include <cstdlib>
//#include "Node.h"
//#include "Iterator.h"
template <typename T>
class DoublyLinkedList <T>
{
public:
class Node
{
private:
T data;
Node *next;
Node *prev;
public:
Node(const T & d, Node * p = nullptr, Node * n = nullptr) : data(d), next(n), prev(p) {}
};
Node *head;
Node *tail;
class Iterator
{
private:
Node *current;
bool reversed;
public:
Iterator(Node * n = head, bool rev = false) : current(n), reversed(rev) {}
Iterator& operator--()
{
if (reversed)
current = current->next;
else
current = current->prev;
return *this;
}
Iterator& operator++()
{
if (reversed)
{
current = current->prev;
}
else
{
current = current->next;
}
return *this;
}
const T& operator *() const
{
return *current;
}
bool operator!=(const Iterator& other) const
{
return (this->current != other->current);
}
bool operator==(const Iterator& other) const
{
return (this->current == other->current);
}
Iterator begin()
{
current = head;
}
Iterator end()
{
current = tail;
}
Iterator rbegin()
{
current = tail;
}
Iterator rend()
{
current = head;
}
};
DoublyLinkedList():head(nullptr), tail(nullptr)
{
}
//
DoublyLinkedList(const DoublyLinkedList& other)
{
for (auto x : other)
push_back(x);
}
//
DoublyLinkedList(initializer_list<T> & list)
{
}
size_t size() const noexcept
{
for (size_t length = 0, iterator iter = iterator::begin(); iter != nullptr; ++iter, ++length) { return length; }
}
void push_back(const T& value)
{
Node * newLast = new Node(value, nullptr, tail);
tail = newLast;
}
void erase(Iterator iter)
{
delete * iter;
}
void remove(const T& val)
{
Iterator iter = Iterator::begin();
while (*iter != nullptr)
{
if (*iter == val)
delete *iter;
else
++iter;
}
}
//
void insert(Iterator iter, const T& value)
{
}
};
class DoublyLinkedList <T>
// ^^^
is wrong in the class declaration. Just omit the <T>:
template <typename T>
class DoublyLinkedList // <<<<< No <T>
{
// ...
};
How about removing <T> at the end of class DoublyLinkedList <T> ?