Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I wrote this implementation of a singly linked list but I don't find my code enough simple and compact. Could you help me to simplify it?

Includes

#include <exception>
#include <iostream>

Structure of data elements

template<typename T>
struct Link {
    T val;
    Link<T>* next;
};

Class of singly linked list

template<typename T>
class SLinkedList {

  public:

    //Default constructor
    SLinkedList(): first_(NULL), size_(0) {}

    //Copy constructor
    SLinkedList(const SLinkedList<T>& other) {
        if(other.first_) {
            first_ = new Link<T>();
            size_ = other.size_;
            Link<T>* index1 = first_;
            Link<T>* index2 = other.first_;
            index1->val = index2->val;

            while(index2->next) {
                index1->next = new Link<T>();
                index1 = index1->next;
                index2 = index2->next;
                index1->val = index2->val;
            }
        }
        else first_ = other.first_;
    }

    //Destructor
    ~SLinkedList() {
        clear();
    }

    //Index operator
    T& operator[](int i) {
        if(first_) {     
            if(i < 0 || i+1 > size_)
                throw std::out_of_range("index out of range!!!");

            if(i == 0) return first_->val;

            Link<T>* index = first_;
            for(int it=0; it<i; it++) index = index->next;
            return index->val;
        }
        else throw std::out_of_range("empty list!!!");
    }

    //Access first element 
    T& front() {
        return first_->val;
    }

    //Access last element
    T& back() {
        return end()->val;
    }

    //Add an element at the begining
    void push_front(const T& value) {
        Link<T>* temp = new Link<T>();
        temp->val = value;
        temp->next = first_;
        first_ = temp;
        size_++;
    }

    //Add an element at the end
    void push_back(const T& value) {
        Link<T>* temp = new Link<T>();
        temp->val = value;
        temp->next = NULL;
        size_++;

        if(!first_) first_ = temp;
        else {
            Link<T>* last = end();
            last->next = temp;
        }
    }

    //Remove elements with specific value
    void remove(const T& value) {
        if(first_) {
            Link<T>* index = first_;
            Link<T>* temp = NULL;

            while(index && index->val == value) {
                index = index->next;
                delete first_;
                first_ = index;
                size_--;
            }

            while(index->next) {
                if(index->next->val == value) {
                    if(index->next->next) {
                        temp = index->next->next;
                        delete index->next;
                        index->next = temp;
                    }
                    else {
                        delete index->next;
                        index->next = NULL;
                    }
                    size_--;
                }
                else index = index->next;
            }
        }
    }

    //Delete first element
    void pop_front() {
        if(first_) {
            Link<T>* first = first_->next;
            delete first_;
            first_ = first;
            size_--;
        }
    }

    //Delete last element
    void pop_back() {
        if(first_) {
            Link<T>* index1 = first_;
            Link<T>* index2 = NULL;
            while(index1->next) {
                index2 = index1;
                index1 = index1->next;
            }
            delete index1;
            index2->next = NULL;
            size_--;
        }
    }

    //Clear content
    void clear() {
        if(first_) {
            Link<T>* index = first_;
            Link<T>* temp = NULL;
            while(index) {
                temp = index;
                index = index->next;
                delete temp;
            }
            size_ = 0;
        }
    }

    //Return size
    int size() const {
        return size_;
    }

    //Invert content
    void reverse() {
        if(first_) {
            Link<T>* first = new Link<T>();
            Link<T>* temp = NULL;
            first->val = first_->val;

            Link<T>* index = first_;

            while(index->next) {
                index = index->next;
                temp = first;
                first = new Link<T>();
                first->val = index->val;
                first->next = temp;
            }
            clear();
            first_ = first;
        }
    }

    //Display elements on output standard
    void print() const {
        Link<T>* index = first_;
        std::cout << index->val;
        while(index->next) {
            index = index->next;
            std::cout << "; " << index->val ;
        }
        std::cout << std::endl;
    }

  private:

    //Access last pointer of data element
    Link<T>* end() const {
        if(!first_) return first_;

        Link<T>* index = first_;

        while(index->next)
            index = index->next;
        return index;
    }

    //First pointer of data element
    Link<T>* first_;

    //Size of list
    int size_;

};
share|improve this question
up vote 4 down vote accepted

Since you're using c++, you should probably be using nullptr rather than NULL.

You also have a few edge case bugs:

  • clear doesn't reset first_ after it has emptied the list (you're currently only calling clear in your code just before you reassign first_ or destroy the class which is why you're probably getting away with it).
  • pop_back doesn't reset first_ if there's only one item in the list.
share|improve this answer

Some advices that you should follow to avoid further bugs:

  • You should verify preconditions, especially in the methods front, back, pop_front, pop_back and throw an std::out_of_range or a std::logic_error if the list is empty before the call.

  • You should add the operator= method. The implicit generated one will generate segmentation faults since the list cells will be shared (and destroyed) by two different lists.

  • You should add a method verifyInvariant (class invariant) that verifies that size_ is the size of the chain starting from first_.

You also can improve the usage of your class with new methods like:

  • You can add a swap method and a copy constructor with xvalue argument to avoid some copies that are not useful.

Inside your methods:

  • You can use auto each time the name of the local variable implicitly gives its type.

  • If you use new Link<T>{} instead of new Link<T>(), all fields of the cells will be 0-initialized, avoiding some undefined behaviors in case of missing code.

  • Think that an exception could be launched on every function/method call, so avoid allocations far from their integration into the list (see push_back) to avoid memory leaks in case of exceptions. Also keep in mind that the class invariant should be preserved in case of exceptions.

  • You could choose better names than index1 and index2 in the copy constructor. We don't know on which list they iterate.

  • The method reverse should not allocate any cell.

Here are the main modifications of the previous remarks on your code. Hope it helps.

For copy, assignment methods:

//Copy constructor
SLinkedList(SLinkedList<T>&& other) { swap(other); }
SLinkedList(const SLinkedList<T>& other) { copy(other); }
SLinkedList& operator=(const SLinkedList<T>& other)
  {  if (this != &other)
         copy(other);
     return *this;
  }
SLinkedList& operator=(SLinkedList<T>&& other)
  {  swap(other);
     return *this;
  }

For access methods:

//Index operator
T& operator[](int i) {
    if(first_) {     
        if(i < 0 || i >= size_)
            throw std::out_of_range("index out of range!!!");
        // more evident to verify that i >=0 and i < size
        if(i == 0) return first_->val;

        auto* index = first_;
        for(int it=0; it<i; it++) index = index->next;
        return index->val;
    }
    else throw std::out_of_range("empty list!!!");
}

//Access first element 
T& front() {
    if (!first_) // verify preconditions
      throw std::logic_error("empty list");
    return first_->val;
}

//Access last element
T& back() {
    if (!first_) // verify preconditions
      throw std::logic_error("empty list");
    return end()->val;
}

For the insertion methods:

//Add an element at the begining
void push_front(const T& value) {
    Link<T>* temp = new Link<T>{};
    temp->val = value;
    temp->next = first_;
    first_ = temp;
    size_++;
}

//Add an element at the end
void push_back(const T& value) {
    Link<T>** insertionPoint = &first_;
    while (*insertionPoint)
        insertionPoint = &((*insertionPoint)->next);

    // allocate it at the last time: this avoids memory leaks
    // in case of exception in the end() method for example.
    Link<T>* temp = new Link<T>{};
    temp->val = value;
    temp->next = nullptr;
    (*insertionPoint) = temp;
    size_++;
}

For the remove methods:

//Delete first element
void pop_front() {
    if(first_) {
        Link<T>* first = first_->next;
        delete first_;
        first_ = first;
        size_--;
    }
    else
      throw std::logic_error("empty list");
}

//Delete last element
void pop_back() {
    if(first_) {
        Link<T>* index = first_;
        Link<T>* previousIndex = nullptr;
        while(index->next) {
            previousIndex = index;
            index = index->next;
        }
        if (first_ == index)
          first_ = index->next;
        delete index;
        previousIndex->next = nullptr;
        size_--;
    }
    else
      throw std::logic_error("empty list");
}

Two implementation for the reverse method:

void reverse() {
    if(first_ && first_->next) {
        // memory leaks in case of exceptions
        auto* previousIndex = nullptr;
        auto* index = first_;
        auto* nextIndex = index->next;
        do {
            index->next = previousIndex;
            previousIndex = index;
            index = nextIndex;
            nextIndex = index->next;
        } while (nextIndex);
        first_ = index;
    };
}

or more exception-safe

void reverse() {
    if(first_ && first_->next) {
        Link<T> temp;
        temp.swap(*this);
        do {
            auto* cell = temp.first_;
            temp.first_ = temp.first_.next;
            --temp.size;
            cell->next = first_;
            first_ = cell;
            ++size_;
        } while (temp.first_);
    };
}

Additional methods:

void swap(Link<T>& other) {
    auto* tempFirst = first_;
    auto* tempSize = size_;
    first_ = other.first_;
    size_ = other.size_;
    other.first_ = tempFirst;
    other.size_ = tempSize;
}

bool verifyInvariant() const {
    int remainingCells = size_;
    auto* index = first_;
    while (--remainingCells >= 0) {
        if (!index)
            return false;
        index = index->next;
    };
    return index == nullptr;
}

private:
void copy(const SLinkedList<T>& other) {
    if (this != &other) {
        clear();
        if(other.first_) {
            first_ = new Link<T>();
            size_ = other.size_;
            auto* indexThis = first_; /* indexThis, indexOther instead of index1, index2 */
            auto* indexOther = other.first_;
            indexThis->val = indexOther->val;

            while(indexOther->next) {
                indexThis->next = new Link<T>{}; /* avoid accidental undefined behavior */
                indexThis = indexOther->next;
                indexOther = indexOther->next;
                indexThis->val = indexOther->val;
            }
        }
        else first_ = other.first_;
    }
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.