I work on own iterator, that should iterate double linked list. If I'm iterating, for cycle is immediately skipped. I haven't errors, but from debugging I don't read much.
for cycle:
for(SemestralWork::DoubleList<Student>::iterator it = link->begin(); it != link->end(); ++it){
//something...
}
iterator + begin() + end():
class iterator {
private:
Node<T> * node;
public:
iterator(){}
iterator(const iterator& cit){}
iterator(T t) {
}
~iterator(){}
iterator& operator=(const iterator& second){
node = second.node;
return(*this);
}
iterator& operator++(){
if (node != NULL){
node = node->GetNext();
}
return(*this);
}
iterator operator++(int){
iterator tmp = *this; //iterator tmp(*this)
operator++();
return tmp;
}
bool operator==(const iterator& second) {
return node == second.node;
}
bool operator!=(const iterator& second) {
return node != second.node;
}
T& operator*() {return node->GetData();}
T* operator->(){return((DoubleList<T>::iterator)*this);}
};
iterator begin(){
return iterator(first->GetData());
}
iterator end(){
return iterator(last->GetData());
}
Node:
template <class U>
class Node{
Node<U> * next;
Node<U> * previous;
U data;
public:
Node(const U &data){
next = NULL;
previous = NULL;
this->data = data;
}
void SetNext(Node<U> *next) { this->next = next; }
Node<U> *GetNext(){ return next; }
void SetPrevious(Node<U> *previous) { this->previous = previous; }
Node<U> *GetPrevious(){ return previous; }
U &GetData() { return data; }
};
Here are a few things I noticed. Whether any of these will actually resolve your problem I don't know because the posted code is incomplete:
Your iterator should not be constructed from a T object and the implementation of the constructor should actually do something (I would guesss the fact that iterator::iterator(T) doesn't do anything at all is your actual problem). Instead, the iterator should be constructed from an Node<T>*.
The preincrement operator should not check if there is actually a next element! It is a precondition for the operator++() that the iterator can be incremented. If anything, the operator should report a misuse with debug settings enabled.
I'm suspicious of your use of last: note that the end iterator is a position one past the last element.
Your comparision oerators should be const members and typically operator!=() just delegates to operator==(), i.e.:
bool iterator::operator!=(iterator const& other) const {
return !(*this == other);
}
The advantage of this implementation is that the operator!=() is consistent with the operator==() even if the implementation of operator==() is changed.
You didn't post the list implementation showing first and last, but I'm assuming last points at the last element. With iterators, end() should point beyond the last element, not to the last element. For example, if the list contains exactly 1 element, your for-loop won't run at all since first == last and therefore begin() == end().
Related
i implemented a custom List including an iterator using the example from a textbook, but when i try to iterate over the list using the iterator, i get the error:
Error C2675 unary '++': 'List::iterator' does not define this
operator or a conversion to a type acceptable to the predefined
operator
does anyone know what's wrong with my class? i'm pretty sure i copied it verbatim from the textbook. thanks!
/*
list class itself - contains links to both ends, size, and many other methods
concrete implementation of List ADT. different from vector because it allows
us to add to middle of list in O(1) time, but does not support O(1) indexing[]
*/
template <typename Object>
class List {
private:
/*
private nested Node class. contains data and pointers to prev/next
along with appropriate constructor methods
struct is a relic from C, essentially a class where members default to public
used to signify a type that contains mostly data to be accessed directly rather
than through methods
Node is private, so only List will be able to access it's fields/methods
*/
struct Node {
Object data;
Node* prev;
Node* next;
Node(const Object& d = Object{}, Node* p = nullptr, Node* n = nullptr) :
data{ d }, prev{ p }, next{ n } {}
Node(Object&& d, Node* p = nullptr, Node* n = nullptr) :
data{ std::move(d) }, prev{ p }, next{ n }
{}
};
public:
/*
const_iterator class. abstracts notion of position, public nested class
const_iterator stores pointer to current node and provides implementation of
basic iterator operations, all in form of overloaded operators like =, ==, !=, ++
*/
class const_iterator {
public:
const_iterator():current{nullptr}{}
const Object& operator*()const
{
return retrieve();
}
const_iterator& operator++()
{
current = current->next;
return *this;
}
const_iterator operator++(int)
{
const_iterator old = *this;
++(*this);
return old;
}
/*
public methods of const_iterator all use operator overloading. operator==,
operator!=, operator* are straightforward. separate routines are necessary for
prefix and postfix versions of operator++ due to their different semantics. we
distinguish between them by their method signatures (empty parameter for prefix,
int parameter for postfix). int is only used to distinguish between them.
in many cases where there is a choice between using prefix or postfix, prefix
version is faster.
*/
bool operator==(const const_iterator& rhs)const
{
return current == rhs.current;
}
bool operator!=(const const_iterator& rhs)const
{
return !(*this == rhs);
}
/*
protected allows classes that inherit from const_iterator to access these fields
but not other classes
*/
protected:
Node* current;
Object& retrieve()const
{
return current->data;
}
const_iterator(Node* p) : current{ p }
{
}
/*
the friend declaration is needed to grant the List class access to const_iterators
nonpublic members
*/
friend class List<Object>;
};
/*
iterator class. abstracts notion of position. public nested class
same functionality as const_iterator, except operator* returns a reference
to the item being viewed, rather than a const reference
iterator can be used in any routine that requires a const_iterator, but not
vice-versa (in other words, iterator IS A const_iterator)
iterator inherits from const_iterator, meaning it inherits all the data and
methods from const_iterator. it can then add new methods and override existing
methods. here we are not adding any new data or changing the behavior of exising
methods. we do add some new methods (with similar signatures to const_iterator)
*/
class iterator : public const_iterator
//inheritance: iterator has same functionality as const_iterator
//iterator can be used wherever const_iterator is needed
{
public:
iterator() {}
/*
do not have to re-implement operator == and operator != (inherited unchanged)
provide a new pair of operator++ implementations that override the original
implementations from const_iterator.
provide an accessor/mutator pair for operator*.
*/
Object& operator*()
{
return const_iterator::retrieve();
}
const Object& operator*()const
{
return const_iterator::operator*();
}
iterator& operator++(int)
{
iterator old = *this;
++(*this);
return old;
}
protected:
/*
protected constructor uses an initialization list to initialize the inherited
current node.
*/
iterator(Node* p) :const_iterator{ p } {}
friend class List<Object>;
};
public:
/*
constructor and big 5. because zero-parameter constructor and copy constructor
must both allocate the header and tail nodes, we provide a init routine.
init creates an empty list.
the destructor reclaims the header and tail nodes, all other nodes, all other
nodes are reclaimed when the destructor invokes clear. similarly, the
copy-constructor is implemented by invoking public methods rather than attempting
low-level pointer manipulations.
*/
List()
{
init();
}
/*
sentinel nodes - makes sense to create an extra node at each end of list to
represent start/end markers. also referred to as header and tail nodes
advantage of sentinel nodes is that it simplifies code by removing special cases
example: without sentinels, removing the first node becomes a special case
because we must reset the list's link to the first node during the remove
operation, and because the remove algorithm in general needs to access the node
prior to the node being removed (and without header, first node does not have
prior)
*/
void init()
{
theSize = 0;
head = new Node;
tail = new Node;
head->next = tail;
tail->prev = head;
}
~List()
{
clear();
delete head;
delete tail;
}
// clear works by repeatedly removing items until the List is empty (uses pop-front)
void clear()
{
while (!empty())
pop_front();
}
List(const List& rhs)
{
init();
for (auto& x : rhs)
{
push_back(x);
}
}
List& operator=(const List& rhs)
{
List copy = rhs;
std::swap(*this, copy);
return *this;
}
List(List&& rhs)
:theSize{ rhs.theSize }, head{ rhs.head }, tail{ rhs.tail }
{
rhs.theSize = 0;
rhs.head = nullptr;
rhs.tail = nullptr;
}
List& operator=(List&& rhs)
{
std::swap(theSize, rhs.theSize);
std::swap(head, rhs.head);
std::swap(tail, rhs.tail);
return *this;
}
// these methods return iterators
iterator begin()
{
return { head->next };
}
const_iterator begin()const
{
return{ head->next };
}
iterator end()
{
return { tail };
}
const_iterator end()const
{
return{ tail };
}
int size()const
{
return theSize;
}
bool empty()const
{
return size() == 0;
}
Object& front()
{
return *begin();
}
const Object& front()const
{
return *begin();
}
Object& back()
{
return *--end();
}
const Object& back() const
{
return *--end();
}
/*
cleverly obtain and use appropriate iterator
insert inserts prior to a position, so push_back inserts prior to the endmarker
pop_back line erase(-end()) creates a temporary iterator corresponding to the
endmarker, retreats the temporary iterator, and uses that iterator to erase.
similar behavior occurs in back
note also that we avoid dealing with node reclamation in pop_front and pop_back
*/
void push_front(const Object& x)
{
insert(begin(), x);
}
void push_front(Object&& x)
{
insert(begin(), std::move(x));
}
void push_back(const Object& x)
{
insert(end(), x);
}
void push_back(Object&& x)
{
insert(end(), std::move(x));
}
void pop_front()
{
erase(begin());
}
void pop_back()
{
erase(--end());
}
/*
inserting a new node between p and p->prev. works by getting a new node
and then changing pointers of p and p-prev in the correct order
also mention usefulness of the sentinels here.
*/
iterator insert(iterator itr, const Object& x)
{
Node* p = itr.current;
theSize++;
Node* newNode = new Node{ x, p->prev,p };
p->prev->next = newNode;
p->prev = newNode;
return newNode;
}
iterator insert(iterator itr, Object&& x)
{
Node* p = itr.current;
theSize++;
p->prev->next = new Node{ std::move(x), p->prev, p };
p->prev = p->prev->next;
return p->prev;
}
/*
erase routines. frst version erases p by rearranging pointers of the nodes just
before and after p, and then returning an iterator representing the item after the
erased element. like insert, erase must also update the size.
second version of erase simply uses an iterator to call the first version of erase,
note - cannot simply use itr++ in 'for' loop and ignore return iterator from erase,
because the value of itr is stale immediately after the call to erase, which is why
erase returns an iterator.
*/
iterator erase(iterator itr)
{
Node* p = itr.current;
iterator retVal{ p->next };
p->prev->next = p->next;
p->next->prev = p->prev;
delete p;
theSize--;
return retVal;
}
iterator erase(iterator from, iterator to)
{
for (iterator itr = from; itr != to;)
{
itr = erase(itr);
}
return to;
}
/*
data members for list - pointers to header and tail nodes. also keeps track of
size in a data member so that the size method can be implemented in constant time
*/
private:
int theSize;
Node* head;
Node* tail;
};
int main()
{
List<int> theList{};
for (int i = 0; i < 10; i++)
theList.push_back(i);
List<int>::iterator iter = theList.begin();
for(int i=0;i<5;i++)
std::cout << *(iter++) << 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
I'm getting segmentation faults when the iterator reaches the last node in class the linked list.
By debugging, I can see that when the iterator reaches the end of the linked list, node->next_ points to null and thus throws the seg fault.
EDIT:
I've include the definition for void push_front() method
List.h
void push_front(const T& value) {
Node* node = new Node(value, nullptr, nullptr);
if (head_ == nullptr) {
head_ = node;
tail_ = head_;
}
else {
node->next_ = head_;
head_ = node;
}
}
I tried to change the overloaded operator to the follow with no luck:
iterator& operator++() {
iNode = iNode->next_; //this line throws the exception
return *this;
}
//and
iterator& operator++() {
return ++(*this);
}
Any help is greatly appreciated!
main.cpp
#include <iostream>
#include "List.h"
#include <string>
int main(){
List<int> l1;
l1.push_front(4);
l1.push_front(3);
l1.push_front(2);
l1.push_front(1);
l1.push_front(0);
for (auto i = l1.begin(); i != l1.end(); ++i)
{
int j = 0;
}
l1.printList();
}
List.h
template<typename T>
class List
{
public:
class Node {
public:
Node(T value, Node* prev, Node* next) : value_(value), prev_(prev), next_(next) {}
T value_;
Node* next_;
Node* prev_;
};
Node* head_;
Node* tail_;
//! An iterator over the list
class iterator
{
public:
Node* iNode;
iterator(Node* head): iNode(head){ }
~iterator() {}
T& operator*() {
return iNode -> value_;
}
//prefix increment
iterator& operator++() {
this->iNode = this->iNode->next_; //this line throws the exception
return *this;
}
//postfix increment
iterator operator++(int ignored) {
iterator result = *this;
++(*this);
return result;
}
bool operator== (const iterator& it) const {
return iNode == it.iNode;
}
bool operator!= (const iterator& it) const {
return !(iNode == it.iNode);
}
};
//! Get an iterator to the beginning of the list
iterator begin() {
return List<T>::iterator(head_);
}
//! Get an iterator just past the end of the list
iterator end() {
return List<T>::iterator(nullptr);
}
};
You need to initialize head_ to nullptr by default:
Node* head_ = nullptr;
otherwise head_ has some indeterminate value, and the following check in push_front is not guaranteed to work:
if (head_ == nullptr)
even though head_ is not pointing to valid memory.
Note that this problem arises even if you never call push_front, because in the for loop check the begin iterator's iNode may not be nullptr, even if the List is empty. This means i will be incremented, which causes UB in the operator++ when accessing next_.
Here's a demo. (If you don't initialize head_, the program segfaults.)
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.
I wrote the following code when trying to make a doubly-linked list with an internal STL-like iterator. I'll just provide the header file with the non-relevant parts trimmed out for now.
My questions are...
The STL uses iterators in a certain way - specifically, you navigate over an STL container from the .begin() iterator up to but not including the .end() iterator. To do this the .end() iterator has to be one-past the end of the container. How would I implement this kind of semantic given what I've started with (this is the main question)?
Is there anything missing in the interface as it stands (with regard to the iterator class and things that should be present in it)?
Here's the code:
template <typename T>
class Node
{
T data;
Node<T>* next;
Node<T>* prev;
};
template <typename T>
class LinkedList
{
public:
class Iterator
{
public:
Iterator() {}
explicit Iterator(const Node<T>& init) { current = init; }
//Dereference operator - return the current node's data.
inline T& operator*() { return current->data; }
//Prefix returns by reference.
inline Iterator& operator++() { current = current->next; return *this; }
inline Iterator& operator--() { current = current->prev; return *this; }
//Postfix returns non-reference and has int parameter to differentiate function signature.
inline Iterator operator++(int) { Iterator res = *this; current = current->next; return res; }
inline Iterator operator--(int) { Iterator res = *this; current = current->prev; return res; }
private:
Node<T>* current;
};
Iterator begin() { return Iterator(m_start); }
Iterator end() { return Iterator(m_end); }
private:
Node<T>* m_start;
Node<T>* m_end;
};
I'm aware that I may or may not have problems with the ++/-- operators, but that doesn't particularly bother me as I'll work those out when I have enough code to do some testing on this. Feel free to drop hints though if you're inclined :)
The iterator returned by end() must be decrementable, otherwise you cannot iterate over the list in reverse.
You could do this, by having Iterator store two pointers: to current node (which would be NULL for end) and to previous node (which allows you to find the last node with data in it, even if current == NULL.
class Iterator
{
public:
Iterator() {}
explicit Iterator(Node<T>* curr, Node<T>* prev):
current(curr), previous(prev) {}
//Dereference operator - return the current node's data.
inline T& operator*() { return current->data; }
//Prefix returns by reference.
inline Iterator& operator++()
{
previous = current;
current = current->next;
return *this;
}
inline Iterator& operator--()
{
current = previous;
previous = current->previous;
return *this;
}
//Postfix should be implemented in terms of prefix operators
inline Iterator operator++(int) { Iterator res = *this; ++*this; return res; }
inline Iterator operator--(int) { Iterator res = *this; --*this; return res; }
private:
Node<T>* current;
Node<T>* previous;
};
Iterator begin() { return Iterator(m_start, 0); }
Iterator end() { return Iterator(0, m_end); }
Alternatively you can have your list contain a sentinel node that designates the "one-past-end" of the list. This node should not have the data member though. This can be achieved by splitting the Node class into a non-template base with only pointers to next and previous node.
For example, it appears that GCC's list implementation stores a pointer to the sentinel, so that its next points to the first item in the list and its prev points to the last item in the list (or both point to itself, if the list is empty).
You are missing operator->, operator== and operator!=, the classification typedefs which can be inherited from std::iterator, a const_iterator implementation (iterator should be implicitly convertible to const_iterator).
First node's prev pointer is NULL, so is last node's next pointer. One-past-the-end's current pointer would be NULL.
operator->
I think NULL will fit here just great.
You may want to write something like for (iterator it = list.begin(); it != list.end(); it++), so you need comparison operators to be defined too.