'Doubly linked list insert method

I am currently doing an assignment to learn about doubly linked lists and iterators. I am making an insert() method that takes in an iterator type, as well as the data to add into the list. However, I'm getting an exception on the line I marked in the insert() method, saying:

Exception thrown: read access violation. iter.node was 0xFFFFFFFFFFFFFFEF

I'm not sure if this is a problem with the iterator or the linked list:

#include <cstdlib>
#include <iostream>
using namespace std;

class List {
    struct Node {
        int data;
        Node* next = nullptr;;
        Node* prev = nullptr;
    };

    friend ostream& operator<<(ostream& os, const List& rhs);

public:

    class iterator {
        friend class List;

        Node* node = nullptr;

    public:
        iterator(Node* node) : node(node) {}

        iterator& operator++() {
            node = node->next;
            return *this;
        }

        iterator& operator--() {
            node = node->prev;
            return *this;
        }

        bool operator==(const iterator& rhs) {
            if (node != rhs.node) {
                return false;
            }
            return true;
        }

        bool operator!=(const iterator& rhs) {
            return !(*this == rhs);
        }

        int operator*() const {
            return node->data;
        }

        iterator& operator->() {
            return *this;
        }
    };

    List() {
        header = new Node();
        trailer = new Node();
        header->next = trailer;
        header->prev = nullptr;
        trailer->prev = header;
        trailer->next = nullptr;
    }

    void push_back(int data) {
        Node* newNode = new Node();
        newNode->data = data;
        newNode->prev = trailer->prev;
        newNode->prev->next = newNode;
        newNode->next = trailer;
        trailer->prev = newNode;
    }

    void pop_back() {
        Node* tempNode = trailer->prev->prev;
        tempNode->next = trailer;
        trailer->prev = tempNode;
    }

    void push_front(int data) {
        Node* newNode = new Node();
        newNode->data = data;
        header->next->prev = newNode;
        newNode->next = header->next;
        newNode->prev = header;
        header->next = newNode;
    }

    void pop_front() {
        Node* tempNode = header->next->next;
        tempNode->prev = header;
        header->next = tempNode;
    }

    int& front() {
        if (header->next == trailer) {
            cerr << "List is empty" << endl;
        }
        return header->next->data;
    }

    int& back() {
        if (trailer->prev == header) {
            cerr << "List is empty" << endl;
        }
        return trailer->prev->data;
    }

    int front() const {
        if (header->next == trailer) {
            cerr << "List is empty" << endl;
        }
        return header->next->data;
    }

    int back() const {
        if (trailer->prev == header) {
            cerr << "List is empty" << endl;
        }
        return trailer->prev->data;
    }

    int size() const {
        int count = 0;
        for (iterator i = begin(); i != end(); ++i) {
            ++count;
        }
        return count;
    }

    int& operator[](int index) {
        int ind = 0;
        for (iterator i = begin(); i != end(); ++i) {
            if (ind == index) {
                return i.node->data;
            }
            ++ind;
        }
    }

    int operator[](int index) const {
        int ind = 0;
        for (iterator i = begin(); i != end(); ++i) {
            if (ind == index) {
                return i.node->data;
            }
            ++ind;
        }
    }

    iterator& begin() {
        iterator iter(header->next);
        return iter;
    }

    iterator& end() {
        iterator iter(trailer);
        return iter;
    }

    iterator begin() const {
        iterator iter(header->next);
        return iter;
    }

    iterator end() const {
        iterator iter(trailer);
        return iter;
    }

    iterator& insert(iterator& iter, int data) {
        Node* newNode = new Node();
        newNode->data = data;
        iter.node->prev->next = newNode; //exception is thrown on this line
        newNode->prev = iter.node->prev;
        newNode->next = iter.node;
        iter.node->prev = newNode;
        iterator newIter(newNode);
        return newIter;
    }

    void clear() {
        Node* iter = header->next;
        Node* next;
        while (iter != trailer) {
            next = iter->next;
            delete iter;
            iter = nullptr;
            iter = next;
        }
        header->next = trailer;
        trailer->prev = header;
    }

    iterator& erase(iterator& iter) {
        for (iterator i = ++begin(); i != end(); ++i) {
            if (i == iter) {
                Node* tempNode = i.node;
                tempNode->prev->next = tempNode->next;
                tempNode->next->prev = tempNode->prev;
                delete tempNode;
                tempNode = nullptr;
                return ++i;
            }
        }
    }

public:
private:
    Node* header;
    Node* trailer;
};

ostream& operator<<(ostream& os, const List& rhs) {
    for (int i : rhs) {
        os << i << " ";
    }
    os << endl;
    return os;
}

void printListInfo(const List& myList) {
    cout << "size: " << myList.size()
        << ", front: " << myList.front()
        << ", back(): " << myList.back()
        << ", list: " << myList << endl;
}

int main() {
    List myList;
    for (int i = 0; i < 10; ++i) myList.insert(myList.end(), i * i);
    printListInfo(myList);
    myList.clear();
    for (int i = 0; i < 10; ++i) myList.insert(myList.begin(), i * i);
    printListInfo(myList);
}


Solution 1:[1]

iterator& iterator::begin() and iterator& iterator::end() are returning references to local variables. Since it's these functions that are being passed to List::insert(...) in main via myList.insert(myList.begin(), ..) etc, the insertion function is always going to be operating on an invalid iterator.

Similarly, iterator::insert has a similar issue in which it is returning a local variable by reference:

iterator& insert(...)
{
    ...
    iterator newIter(newNode);
    return newIter;
}

Once these functions exit, the local variables are destroyed and so those references that were returned are left dangling - pointing at memory where an object used to be.

A quick fix would be to make sure that those new iterators are not local to that function and are instead initialized on the heap e.g.

iterator& begin() {
    iterator* iter = new iterator(header->next);
    return *iter;
}

iterator& end() {
    iterator* iter = new iterator(trailer);
    return *iter;
}

However, this also means that you would have to introduce a way to keep track of those iterators so that you can free the memory once an iterator is no longer needed, perhaps by storing the iterators as a member variable of List instead of storing the begin/end Nodes of List and therefore only accessing the Nodes via the iterators.


I found this due to compiling with warnings on, which is highly recommended!

In member function 'List::iterator& List::begin()':
<source>:151:16: warning: reference to local variable 'iter' returned [-Wreturn-local-addr]
  151 |         return iter;
      |                ^~~~
<source>:150:18: note: declared here
  150 |         iterator iter(header->next);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1