Singly Linked List in C++ I've written a singly linked list in C++. Would appreciate reviews on code efficiency and best practices. 

#ifndef SLIST_HPP
#define SLIST_HPP

#include &lt;stdexcept&gt;
#include &lt;iostream&gt;

template &lt;typename T&gt; struct node{
    node(T data):data(data),next(nullptr){}
    T data;
    node&lt;T&gt; * next;
};

template&lt; typename T&gt; class slist{
    node&lt;T&gt;* head;
    int size;
public:
    slist(node&lt;T&gt;* head):head(head), size(0){}

    slist(const slist&lt;T&gt;&amp; rhs){

     node&lt;T&gt;* temp = rhs.getHead();

    node&lt;T&gt;* current = new node&lt;T&gt;(temp-&gt;data);
    while(temp != nullptr){

        current-&gt;next = new node&lt;T&gt;(temp-&gt;next-&gt;data);
        temp = temp-&gt;next;
        current = current-&gt;next;
    }
}
    ~slist(){
        if(head == nullptr) return;

        while(head != nullptr){
            node&lt;T&gt;* current = head;
            head = head-&gt;next;
            delete(current);
        }
    }
    slist&amp; operator= (const slist&amp; rhs){

    node&lt;T&gt;* temp = rhs.getHead();
    try{
        if(temp == nullptr) throw std::runtime_error("Can't assign null");
    }catch(std::runtime_error&amp; e){
        std::cout&lt;&lt;e.what()&lt;&lt;std::endl;
    }

    node&lt;T&gt;* current = head;
    while(temp != nullptr){

        temp = temp-&gt;next;
        current = current-&gt;next;
        current-&gt;next = new node&lt;T&gt;(temp-&gt;next-&gt;data);
    }


}
    node&lt;T&gt;* getHead()const {
        return head;
    }

    void add(node&lt;T&gt;* p, T item){
        if(p == nullptr) return;
        node&lt;T&gt;* current = new node&lt;T&gt;(item);
        node&lt;T&gt;* temp = p-&gt;next;
        p-&gt;next = current;
        current-&gt;next = temp;
        size++;

    }
    void insertFront(T item){
        node&lt;T&gt;* p = new node&lt;T&gt;(item);
        if(head == nullptr){
            p = head;
            size++;
            return;
        }
        p-&gt;next = head;
        head = p;
        size++;
    }

    void insertBack(T item){
        node&lt;T&gt;* p = new node&lt;T&gt;(item);
        node&lt;T&gt;* current = head;
        while(current-&gt;next != nullptr){
            current = current-&gt;next;
        }
        current-&gt;next = p;
        size++;
    }

    void remove(T item){
        bool check = false;

        node&lt;T&gt;* current = head;
        try {
            while(current != nullptr){
                if(current-&gt;data == item) check = true;
                current = current-&gt;next;
            }
            if(!check){
                throw std::runtime_error("Item not in list");

            }
        }catch(std::runtime_error&amp; e){
            std::cout&lt;&lt;e.what()&lt;&lt;std::endl;
            exit(-1);
        }

        current = head;
        while(current != nullptr){
           if(current-&gt;next-&gt;data == item){
               node&lt;T&gt;* temp = current-&gt;next;
               current-&gt;next = current-&gt;next-&gt;next;
               delete(temp);
               break;
           }
            current = current-&gt;next;
        }
        size--;

    }
    int getSize () const {
        return size;
    }

    void printList(){
        node&lt;T&gt;* current = head;
        while(current != nullptr){
            if(current-&gt;next != nullptr){
                std::cout&lt;&lt;current-&gt;data&lt;&lt;"-&gt;";
            }else{
                std::cout&lt;&lt;current-&gt;data&lt;&lt;std::endl;
            }
            current = current-&gt;next;
        }
    }
};
#endif //SLIST_HPP
You're reimplementing std::forward_list, so a good place to start would be the API documentation for forward_list. That'll show you a lot of deficiencies in your current approach:


Naming. For example, getSize() should be size(), so that you can write generic algorithms that work with your class in addition to std::list, std::vector, etc (all of which have a size() method).
Encapsulation. You provide a public member function getHead() that returns a raw pointer to an internal node. What do you expect the user to be able to do with a node&lt;T&gt;*?
Composability. Your remove member function causes an exit(-1) on failure. It would be much much better to have remove throw an exception, and then allow the caller to catch and handle the exception — possibly with an exit, but possibly in some other way.
Support common C++ idioms. Your list class doesn't provide begin() and end(), so it's not iterable; e.g. I can't say for (auto&amp;&amp; element : myList) { std::cout &lt;&lt; element; }. If you provide iteration, then I can write this loop, so I won't need your printList member function anymore.


Finally, one more naming nit: As a member function, printList could just be named print. myList.print() is slightly better (shorter and less repetitive) than myList.printList(). And again it enables generic programming:

// You could overload this printTwice function for each container type:

void printTwice(const List&amp; l)
{
    l.printList();
    l.printList();
}

void printTwice(const Vector&amp; l)
{
    l.printVector();
    l.printVector();
}

// ^ But yuck. Prefer genericity:

template&lt;class Container&gt;
void printTwice(const Container&amp; c)
{
    c.print();
    c.print();
}

// ^ In fact, prefer to use non-member functions for anything that
// doesn't require special access to the innards of the class:

template&lt;class Container&gt;
void printTwice(const Container&amp; c)
{
    print(c);
    print(c);
}

// ^ In fact, prefer to spell the verb "print" in the standard way[*]:

template&lt;class Container&gt;
void printTwice(const Container&amp; c)
{
    std::cout &lt;&lt; c &lt;&lt; std::endl;
    std::cout &lt;&lt; c &lt;&lt; std::endl;
}


[* – unless you're really concerned about efficiency, in which case you should probably stay away from iostreams; but at that point you're not doing generic programming anymore anyway]