I've only ever written C++98 code, because that's what I learned and at work we are limited to C++98 due to the fact that we sell source code to customers who are reluctant to upgrade their toolchains.
Therefore, I decided to learn modern C++ in my own time. As a first exercise I decided to implement a linked list using as many modern features of C++ that I'm aware of. The code passes my unit tests and valgrind says I have no memory leaks, but I would like a review of my code, because I would like to know if I wrote "good" modern C++.
I would really appreciate if someone could please review the code:
#include <memory>
using std::shared_ptr;
namespace ds {
class NullNextException : public std::exception { };
template <class T>
class Node {
public:
Node(T _value, shared_ptr< Node<T> > _next = nullptr) noexcept :
value(_value),
next(_next) {
}
Node(const Node<T> &) = delete;
Node<T> &operator = (const Node<T> &) = delete;
const T &get() const noexcept {
return value;
}
const Node<T> &get_next() const {
if (next == nullptr) {
throw NullNextException();
}
return *next;
}
const shared_ptr< Node<T> > &get_next_ptr() const noexcept {
return next;
}
bool end() const noexcept {
return (next == nullptr);
}
void set(T &_value) noexcept {
value = _value;
}
void set_next(shared_ptr< Node<T> > _next) noexcept {
next = _next;
}
private:
T value;
shared_ptr< Node<T> > next;
};
class EmptyListException : public std::exception { };
class OutOfBoundsException : public std::exception { };
template <class T>
class List {
public:
List() noexcept :
front(nullptr),
n_nodes(0) {
}
List(const List<T> &) = delete;
List<T> &operator = (const List<T> &) = delete;
void insert_front(T &value) {
auto node = shared_ptr< Node<T> >(new Node<T>(value, front));
front = std::move(node);
n_nodes++;
}
void insert_back(T &value) {
if (front == nullptr) {
insert_front(value);
return;
}
auto node = shared_ptr< Node<T> >(new Node<T>(value));
auto back = front;
while (!back->end()) {
back = back->get_next_ptr();
}
back->set_next(node);
n_nodes++;
}
T pop_front() {
if (front == nullptr) {
throw EmptyListException();
}
T retval = std::move(front->get());
front = std::move(front->get_next_ptr());
n_nodes--;
return std::move(retval);
}
T pop_back() {
if (front == nullptr) {
throw EmptyListException();
}
auto penult = front;
auto back = front;
if (!back->end()) {
back = back->get_next_ptr();
}
while (!back->end()) {
penult = back;
back = back->get_next_ptr();
}
T retval = std::move(back->get());
if (back == front) {
front = nullptr;
}
else {
penult->set_next(nullptr);
}
n_nodes--;
return std::move(retval);
}
const T &nth(size_t n) const {
if (n >= size()) {
throw OutOfBoundsException();
}
auto ith = front;
for (size_t ii = 0; ii < n; ++ii) {
ith = ith->get_next_ptr();
}
return ith->get();
}
size_t size() const noexcept {
return n_nodes;
}
private:
shared_ptr< Node<T> > front;
size_t n_nodes;
};
}