Peer-reviewed code snippets that anyone can edit
A wiki for useful code snippets
Template Linked List With Iterators

Description

This is a template implementation of a singly linked list that strives to be compatible with C++'s standard template library by implementing a compatible iterator class.

The class itself if implemented in a header file, below the header file will be a program designed to informally test the linked list class.

LinkedListTemplate.hpp

This file implements the linked list and the iterator classes.

#ifndef LINKED_LIST_TEMPLATE_CLASS_HPP
#define LINKED_LIST_TEMPLATE_CLASS_HPP
#pragma once
 
#include <iterator>
 
//This class will be referenced in the LinkedList class so it need to be declared before it can be used...
//    it uses the LinkedList class in its implementation so it can not be defined first.
template<typename T, typename T_REF, typename T_PTR> class LinkedListIterator;
 
//We want the LinkedList Class to work for multiple different types of objects...
//So we will create a template
template <typename T>
class LinkedList {
    //This will simplify things when we need to refer to the current class's type...
    typedef LinkedList<T> _ThisType;
 
    // First let us define the inner classes use: 
    //     LinkedListNode - protected (because the LinkedList Class and its decendants are the only classes that should use it)
    //     LinkedListIterator - public because other classes will use it to interact with the linked list.
    protected:
    //inner class... Associate an element with a pointer to the next element...
    struct LinkedListNode {
        T data;
        LinkedListNode *next;
 
        //Constructors useful for initializing members
        explicit LinkedListNode(const T &element) : data(element), next(NULL) { } //Make sure that next defaults to NULL...
        LinkedListNode(const T &element, LinkedListNode *nxt) : data(element), next(nxt) { } //Make sure that next defaults to NULL...
        //Special function for appending a node to the current node.
        LinkedListNode& insertNext(LinkedListNode *nxt) { 
            if (nxt!=NULL) {
                LinkedListNode *temp = next;
                next = nxt;
                nxt->next = temp;
            }
            return *this;
        }
        //get the data 
        operator T() { return data; }        
    };
 
 
    public:
 
    //Define the iterator class as friend (much easier than making it nested...)
    // note that these definitions illistrate why the iterator template uses three parameters.
    friend class LinkedListIterator<T, T&, T*>;
    friend class LinkedListIterator<T, const T&, const T*>; //Here we want the reference and pointers to be const, but not the data!
 
    //define some friendly names for various versions of the iterator class
    typedef LinkedListIterator<T, T&, T*> iterator;
    typedef LinkedListIterator<T, const T&, const T*> const_iterator;
 
    //Allow access to the the template type.. 
    typedef T value_type; 
    typedef unsigned int size_t;
 
    protected:
    //Each linked list has a root node pointer...
    LinkedListNode *root;
    //Keeping track of the last node will speed some operations up...
    LinkedListNode *lastNode;
 
    //Private function to update the lastNode
    void setLastNode() { 
        if(lastNode == NULL) { lastNode = root; }
        while(lastNode->next) { lastNode = lastNode->next; }
    }
 
    public:
    //Default constructor... set root to null!
    LinkedList() : root(NULL), lastNode(NULL) { }
 
    //constructor to initialize with a given number of nodes using value_type's default constructor.
    explicit LinkedList(size_t size) : root(NULL), lastNode(NULL) {
        for(size_t i=0; i < size; ++i) {
            push_top(value_type());
        }
    }
 
    //constructor to initialize with a given number of nodes with a default value.
    //   value_type must have a copy constructor... is this constructor necessary?
    LinkedList(size_t size, value_type defValue) : root(NULL), lastNode(NULL) {
        for(size_t i=0; i < size; ++i) {
            push_top(value_type(defValue));
        }
    }
 
    //Deconstructor... We need to scroll though the list and "delete" any nodes we find.
    ~LinkedList() {
        LinkedListNode *ptr = root;
        while (ptr) {
            //********* Debugging line *************
            //std::cout << "killing: " << ptr->data << (ptr->next ? "-->next": "-->NULL") << std::endl;
            root = ptr->next;
            delete ptr;
            ptr = root;
        }
 
    }
 
    //Note that if you need a deconstructor, you generally also need a copy constructor and an assignment operator...
    // This is a copy constructor that will make a copy of a LinkedList by coping over all of the data...
    LinkedList(const LinkedList<T>& rhs) { 
        LinkedListNode *ptr = rhs.root;
        if(ptr) {
            root = new LinkedListNode(ptr->data);
            ptr = ptr->next;
            lastNode = root;
            //****** debug ****
            //std::cout <<"(copy) created: " <<root->data << (ptr->next ? "-->next": "-->NULL") << std::endl;
            while(ptr) {
                lastNode->next = new LinkedListNode(ptr->data);
                lastNode = lastNode->next; 
                //****** debug****
                //std::cout <<"(copy) created: " <<thisptr->data << (ptr->next ? "-->next": "-->NULL") <<std::endl;
                ptr = ptr->next;
            }
        }
    }
 
    //If you need a copy constructor, you also need an assignment operator...
    // This will delete the current contents of the list, and copy over the data from the other list...
    _ThisType &operator=(const LinkedList<T>& rhs) {
        //self-assignment is ignored...
        if (&rhs == this) { return *this; }
        //first of all, get rid of any data currently in the list...
        while (pop_top()) { /* do nothing */ }
        //Next construct a new list from the data in the rhs...
        LinkedListNode *ptr = rhs.root;
        if(ptr) {
            root = new LinkedListNode(ptr->data); 
            //****** debug ****
            //std::cout <<"(assign) created: " <<root->data <<std::endl;
            ptr = ptr->next;
            lastNode = root;
            while(ptr) {
                lastNode->next = new LinkedListNode(ptr->data);
                lastNode = lastNode->next; 
                //****** debug****
                //std::cout <<"(assign) created: " <<thisptr->data << (ptr->next ? "-->next": "-->NULL") <<std::endl;
                ptr = ptr->next;
            }
        }
        return *this;   
    }
 
 
    // Add elements to the top/front of the list (i.e. create a new root element)
    _ThisType &push_top(value_type element = value_type()) {
        //first check if root is null... if it is then this is where we add the element....
        if (!root) {
            root = new LinkedListNode(element);
            lastNode = root;
        } else { // else we create a new root and push everything else down the line...
            LinkedListNode *temp = root;
            root = new LinkedListNode(element, temp);
        }
        return *this;
    }
 
    // Add elements to the bottom/back of the list (i.e. append a new node)
    // Scroll though the list looking for the place where "next" is NULL
    _ThisType &push_back(value_type element = value_type()) {
        //first check if root is null... if it is then this is where we add the element....
        if (!root) {
            root = new LinkedListNode(element);
            lastNode = root;
        } else { // else we need to look for ptr->next == null...
            lastNode->insertNext(new LinkedListNode(element));
            lastNode = lastNode->next;
        }
        return *this;
    }
 
 
    bool pop_top() {
        if(root) {
            LinkedListNode* temp = root;
            root = root->next; //node is no longer part of the list...
            delete temp;
            if (root == NULL) { lastNode = NULL; }
            return true;
        }
        return false;
    }
 
    bool pop_top(int n) {
        bool retVal = true;
        while (n-- > 0 && retVal) { retVal = pop_top(); }
        return retVal;
    }
 
    bool pop_back() {
        if(root) {
            if(root==lastNode) { 
                delete root;
                root = NULL;
                lastNode = NULL;
                return true;
            }
            LinkedListNode* ptr = root;
            while(ptr->next != lastNode) { ptr = ptr->next; } //after this loop ptr will be the second to last node
            //lastNode is the last node, ptr is the next-to-last node...
            ptr->next = NULL;
            delete lastNode;
            lastNode = ptr;
            return true;
        }
        return false;
    }
 
    bool pop_back(int n) {
        bool retVal = true;
        while (n-- > 0 && retVal) { retVal = pop_back(); }
        return retVal;
    }
 
 
    //inserts
    _ThisType& insertAfter(iterator here, const value_type &item) {
        if (here) {
            LinkedListNode* newNode = new LinkedListNode(item, here.current_node->next);
            if (here.hasParent()) {
                if (here.current_node == here.getPtrToParent()->lastNode) { here.getPtrToParent()->lastNode = newNode; }
            }
            here.current_node->next = newNode;
        } else {
            if (here.hasParent()) { here.getPtrToParent()->push_back(item); } else { push_back(item); }
        }
        return *this;
    }
 
    _ThisType& insert(iterator here, const value_type &item) {
        //std::cout << "parent: " << here.parent << " list: " << this << std::endl;
        if (!here) { 
            if (here.hasParent()) { 
                here.getPtrToParent()->push_back(item); 
            } else { 
                push_back(item); 
            }
        } else {
            if (here.getPtrToParent() == this) { //does this belong to the current list? If we 
                if (here.current_node == root) { 
                    push_top(item); 
                } else {
                    LinkedListNode* ptr = root;
                    while(ptr->next!=here.current_node) { ptr=ptr->next; } //find the node before here.current_node
                    ptr->next = new LinkedListNode(item, here.current_node); // insert the node
                    if(ptr==lastNode) { lastNode = lastNode->next; }
                }
            } else {
                if(here.hasParent()) { here.getPtrToParent()->insert(here, item); } else { push_back(item); }
            }
        }
        return *this;
    }
 
    _ThisType &operator+=(const _ThisType &list) {
        const_iterator theEnd = list.end();
        for(const_iterator iter = list.begin(); iter != theEnd; ++iter) {
            push_back(*iter);
        }
        return *this;
    }
 
    iterator find(const value_type &item) {
        iterator iter = begin();
        iterator theEnd = end();
        for(; iter!=theEnd; ++iter) {
            if(*iter == item) { return iter; }
        }
    }
 
    const_iterator find(const value_type &item) const {
        const_iterator iter = begin();
        const_iterator theEnd = end();
        for(; iter!=theEnd; ++iter) {
            if(*iter == item) { return iter; }
        }
    }
 
    bool isEmpty() const { return root == NULL; }
    iterator begin()  { return iterator(root, *this); }
    iterator end()    { return NULL; }
    iterator last()   { return iterator(lastNode, *this); }
    const_iterator begin() const { return const_iterator(root, *this); }
    const_iterator end() const   { return NULL; }
    const_iterator last() const  { return const_iterator(lastNode, *this); }
};
 
template<typename T, typename T_REF, typename T_PTR>
class LinkedListIterator: public std::iterator<std::forward_iterator_tag, T> {
    friend class LinkedList<T>;
    typedef typename LinkedList<T>::LinkedListNode ListNode;
    typedef LinkedListIterator<T, T_REF, T_PTR> _ThisType;
    ListNode *current_node;
    LinkedList<T> *parent;
 
    public:
    typedef T value_type;       
    LinkedListIterator() : current_node(NULL), parent(NULL) { }
    LinkedListIterator(ListNode* node) : current_node(node), parent(NULL) { }
    LinkedListIterator(ListNode* node, const LinkedList<T>& parentList) : current_node(node), parent(const_cast<LinkedList<T>*>(&parentList)) { }
    LinkedListIterator(const _ThisType& iter) : current_node(iter.current_node), parent(iter.parent) { }
 
    bool operator==(const _ThisType& rhs) const {
        return current_node == rhs.current_node;
    }
 
    bool operator!=(const _ThisType& rhs) const {
        return current_node != rhs.current_node;
    }
 
    _ThisType& operator++() {
        if(current_node!=NULL) {
            current_node = current_node->next;
        }
        return *this;
    }
 
    _ThisType operator++(int) {
        //we want to ensure the iter++ operator returns the current value, and THEN updates...
        _ThisType temp = *this; //so we create a temp and assign the current iterator
        if(current_node!=NULL) {
            current_node = current_node->next; //increment this.
        }
        return temp; //return the copy...
    }
 
 
    T_REF operator*() const { return current_node->data; }
    T_PTR operator->() const { return &(operator*()); }
 
    bool isValid() { return current_node!=NULL; }
    bool isLast() { return (current_node!=NULL) ? current_node->next == NULL : true; }
    operator void*() { return current_node; }
 
    LinkedList<T> *getPtrToParent(){ return parent; }
    bool hasParent() { return parent!=NULL; }
    void setParent(const LinkedList<T>& parentList) { 
        if (this->parent != &parentList) {
            this->parnet = &parentList;
            current_node = parentList.begin();
        }
    }
 
};
 
#endif

TestCode.cpp

This file contains code used to informally test the LinkedList class and interators.

#include <iostream>
#include <string>
#include "LinkedListTemplate.hpp"
 
template<typename T>
std::ostream& operator<<(std::ostream& out, const LinkedList<T> &list) {
    typename LinkedList<T>::const_iterator iter = list.begin();
    typename LinkedList<T>::const_iterator theEnd = list.end();
    if (iter==theEnd) { 
        out << "NULL"; 
    } else {
        while(!iter.isLast()) {
            out << *iter++ << " --> ";
        }
        out << *iter << " --> NULL.";
    }
    return out;
}
 
int main() {
    //Lets make sure things work with another type...
    LinkedList<int> listOfInts;
    LinkedList<int> listOfInts2;
    listOfInts.push_back(1).push_back(2).push_back(3).push_back(4);
    std::cout << "List of ints: " << listOfInts << std::endl;
    //Lets make sure the assignment operator works...
    listOfInts2 = listOfInts;
    std::cout << "Assignment: " << listOfInts2 << std::endl;
    listOfInts2.pop_back(); 
    listOfInts2.pop_top();
    std::cout << "remove First and Last: " << listOfInts2 << std::endl;
    LinkedList<int> listOfInts3;
    listOfInts3.push_top(1).push_top(2).push_top(3).push_top(4);
    std::cout << "push top: " << listOfInts3 << std::endl;
    listOfInts3.pop_top();
    std::cout << "pop top: " << listOfInts3 << std::endl;
 
    listOfInts3.pop_back();
    std::cout << "pop back: " << listOfInts3 << std::endl;
    listOfInts3.insertAfter(listOfInts3.begin(), 8);
    std::cout << "insertAfter begin(): " << listOfInts3 << std::endl;
    listOfInts3.insertAfter(listOfInts3.end(), 10);
    std::cout << "insertAfter end(): " << listOfInts3 << std::endl;
 
    listOfInts3.insert(listOfInts3.begin(), 5);
    std::cout << "insert begin(): " << listOfInts3 << std::endl;
    listOfInts3.insert(++listOfInts3.begin(), 6);
    std::cout << "insert ++begin(): " << listOfInts3 << std::endl;
 
 
    listOfInts3 += listOfInts2;
    std::cout << "insert ++begin(): " << listOfInts3 << std::endl;
 
    return 0;
}