This seems to work fine, but I'm very new to C++ and would like any suggestions for improvements. Areas I have the most trouble with are:
Namespacing (honestly, it's still half looking stuff up and making an educated guess with typename blah blah blah). Is there a "cleaner" way to implement this?
Various C++ idioms (copy and swap) that aren't as common in, say, C or Java. There are good answers relating to copy and swap in particular, but I just don't quite "get it" yet. The principal makes sense, but specifically what to do with regards to protecting against exceptions in the copy constructor itself
Adding to that, exceptions in general in C++ (coming from the strict Java approach and the "check errno/return value/whatever" C approach), although in the context of this code it's more about guaranteeing exception safety where people would expect it (see above).
General good "style". Specifically, what's the cleanest way to implement the
Node
class in data structures where we see something like this? this plays into how namespacing can get tricky. There seems to be a tradeoff of encapsulation (e.g. defining a struct node within the LinkedList class seems intuitively more "reasonable", but this makes dealing with nodes in any function that may be written outside my definition file strewn with typename).- Another example: should
root
really be aunique_ptr
here instead of a regular object? It certainly made my code easier to write, but I'm curious as to arguments for or against it.
- Another example: should
I'm still fairly unclear as to when exactly I need the template declaration for classes (see the copy constructor for instance, where it takes
LinkedList
as an argument instead ofLinkedList<T>
, but it worked, which seems odd).
I understand the use case between e.g. unique_ptr and shared_ptr for the most part, but certainly want to know if I'm misusing anything here. There is nothing fancy (like a reference to the tail to make insertion faster), but I just want to make sure I'm starting out on the right foot as the more I use C++ it seems the less I actually understand anything.
LinkedList class header
#ifndef _NEW_LL_H #define _NEW_LL_H #include<memory> #include "node.h" template <typename T> class LinkedList { public: LinkedList() {}; ~LinkedList() {}; // destructor LinkedList(LinkedList const & ll); // copy constructor LinkedList& operator=(LinkedList const & ll); // copy assignment LinkedList(LinkedList && ll); // move constructor LinkedList& operator=(LinkedList && ll); // move assignment void append(T const& item); // deletes the first node containing item, returns true if successful bool delete_node(T const& item); void print(); bool search(T const& item); std::unique_ptr<typename Node<T>::Node> root; // std::unique_ptr<Node> tail; maybe later }; #endif
Node header
#ifndef _NODE_H #define _NODE_H #include<memory> template <typename T> class Node { public: T item; std::unique_ptr<Node> next=nullptr; Node(T const& t); // default constructor Node(Node const& insert); // copy constructor }; #endif
Implementation file
#include <iostream> #include <memory> #include "new_ll.h" using namespace std; template <typename T> LinkedList<T>::LinkedList(LinkedList const & ll) { if(ll.root) root = make_unique<Node<T>>(*ll.root); } // copy constructor calls node's recursively template <typename T> LinkedList<T>& LinkedList<T>::operator=(LinkedList<T> const & ll) { if(ll.root) root = make_unique<Node>(*ll.root); } // copy assignment template <typename T> LinkedList<T>::LinkedList(LinkedList<T> && ll) { root = move(ll.root); } // move constructor template <typename T> LinkedList<T>& LinkedList<T>::operator=(LinkedList<T> && ll) { root = move(ll.root); } // move assignment template <typename T> void LinkedList<T>::append(T const& item) { if(root==nullptr) { root = make_unique<Node<T>>(item); return; } Node<T> *tmpNode = root.get(); while(tmpNode->next!=nullptr) tmpNode=tmpNode->next.get(); tmpNode->next = make_unique<Node<T>>(item); } template <typename T> bool LinkedList<T>::delete_node(T const& item) { if(root->item == item) { root = move(root->next); return true; } Node<T> *tmpNode = root.get(); while(tmpNode->next!=nullptr) { if(tmpNode->next->item == item) { tmpNode->next = move(tmpNode->next->next); return true; } tmpNode = tmpNode->next.get(); } return false; } template <typename T> void LinkedList<T>::print() { Node<T> *tmpNode = root.get(); while(tmpNode!=nullptr) { cout << "Address: " << tmpNode << " value: " << tmpNode->item << endl; tmpNode = tmpNode->next.get(); } } template <typename T> bool LinkedList<T>::search(T const& item) { Node<T> *tmpNode = root.get(); while(tmpNode!=nullptr) { if(tmpNode->item == item) return true; tmpNode = tmpNode->next.get(); } return false; } template <typename T> Node<T>::Node(T const& t) : item(t) {}; // default constructor template <typename T> Node<T>::Node(Node const& insert) : item(insert.item) { if(insert.next) next = make_unique<Node<T>>(*insert.next); }; // copy constructor
Any input is appreciated! I just feel like an idiot as I seem to constantly be learning and forgetting "good" C++ practice.
Node
class in data structures where we see something like this? this plays into how namespacing can get tricky."\$\endgroup\$std::remove_reference<T>::type
is a type, because there might be a specific version of remove_reference with a certain T for which type isn't a type (it could be an enumeration value).\$\endgroup\$