When reviewing my code, please consider these questions too:
Any way to avoid using new/delete? Or are data structures like that normally and can't really use smart pointers? I was thinking of using shared pointers or unique pointers but they seem odd to use in this case.
Recursion in a constructor? Good or bad?
Copy constructor should be deleted for Node? Because you copy the pointer to next otherwise. Copy of the nodes should be done by Queue instead?
queue.h
#ifndef H_UTILS_QUEUE_H
#define H_UTILS_QUEUE_H
#include <stdexcept>
namespace Utils {
template<class T>
class Queue {
private:
// Node
class Node {
public:
explicit Node(const T& data, Node* next);
explicit Node(T&& data, Node* next);
Node(const Node& other);
Node(Node&& other) noexcept;
Node& operator=(Node other) noexcept;
private:
void swap(Node& other) noexcept;
public:
T mData;
Node* mNext;
}; // Node
public:
Queue();
Queue(const Queue<T>& other);
Queue(Queue<T>&& other) noexcept;
Queue<T>& operator=(Queue<T> other) noexcept;
T& front();
void pop_front() noexcept;
void push_front(const T& data);
void push_back(const T& data);
void push_back(T&& data);
private:
void swap(Queue<T>& other) noexcept;
Node* deepCopy(Node* node);
private:
Node* mHead;
}; // Queue
// Node
template<class T>
Queue<T>::Node::Node(const T& data, typename Queue<T>::Node* next)
: mData(data)
, mNext(next)
{
}
template<class T>
Queue<T>::Node::Node(T&& data, typename Queue<T>::Node* next)
: mData(std::move(data))
, mNext(next)
{
}
template<class T>
Queue<T>::Node::Node(typename const Queue<T>::Node& other)
: mData()
, mNext(nullptr)
{
mData = other.mData;
mNext = other.mNext;
}
template<class T>
Queue<T>::Node::Node(typename Queue<T>::Node&& other) noexcept
: mData()
, mNext(nullptr)
{
swap(other);
}
template<class T>
typename Queue<T>::Node& Queue<T>::Node::operator=(typename Queue<T>::Node other) noexcept
{
if (this != &other) {
swap(node);
}
return *this
}
template<class T>
void Queue<T>::Node::swap(typename Queue<T>::Node& other) noexcept
{
using std::swap;
swap(mData, other.mData);
swap(mNext, other.mNext);
}
// Queue
template<class T>
Queue<T>::Queue()
: mHead(nullptr)
{
}
template<class T>
Queue<T>::Queue(const Queue<T>& other)
: mHead(nullptr)
{
if (other.mHead) {
mHead = deepCopy(other.mHead);
}
}
template<class T>
Queue<T>::Queue(Queue<T>&& other) noexcept
: mHead(nullptr)
{
swap(other);
}
template<class T>
Queue<T>& Queue<T>::operator=(Queue<T> other) noexcept
{
if (this != &other) {
swap(other);
}
return *this;
}
template<class T>
void Queue<T>::swap(Queue<T>& other) noexcept
{
using std::swap;
swap(mHead, other.mHead);
}
template<class T>
typename Queue<T>::Node* Queue<T>::deepCopy(typename Queue<T>::Node* node)
{
if (node->mNext == nullptr) {
// at tail
// if an exception is thrown here allow it to progate out of this function
// to be handled by the calling code
// no memory will be leaked
return new Node(node->mData, nullptr);
}
Node* next = deepCopy(node->mNext);
Node* current = nullptr;
try {
current = new Node(node->mData, next);
} catch (...) {
// something went wrong
// recursively deallocate any nodes that have been allocated up to this point
Node* i = next;
while (i) {
Node* copy = i->mNext;
delete i;
i = copy;
}
}
return current;
}
template<class T>
T& Queue<T>::front()
{
if (mHead) {
return mHead->mData;
} else {
throw std::logic_error("trying to get front of empty queue");
}
}
template<class T>
void Queue<T>::pop_front() noexcept
{
if (mHead) {
Node* newHead = mHead->mNext;
Node* oldHead = mHead;
mHead = newHead;
delete oldHead;
}
}
template<class T>
void Queue<T>::push_front(const T& data)
{
try {
Node* prevHead = mHead;
Node* newHead = new Node(data, prevHead);
// only once we successfully get to this stage do we set the head pointer
mHead = newHead;
} catch (...) {
// something went wrong
// queue still in same state it was before call to push_back
// let the caller handle the exception
throw;
}
}
template<class T>
void Queue<T>::push_back(const T& data)
{
// find the tail
Node* tail = mHead;
while (tail) {
if (tail->mNext == nullptr) break;
tail = tail->mNext;
}
if (tail) {
Node* newHead = nullptr;
try {
newHead = new Node(data, nullptr);
} catch (...) {
// no changes made
// no leaks
// propogate to caller
throw;
}
tail->mNext = newHead;
} else {
Node* newHead = nullptr;
try {
newHead = new Node(data, nullptr);
} catch (...) {
// no changes made
// no leaks
// propogate to caller
throw;
}
mHead = newHead;
}
}
template<class T>
void Queue<T>::push_back(T&& data)
{
// find the tail
Node* tail = mHead;
while (tail) {
if (tail->mNext == nullptr) break;
tail = tail->mNext;
}
if (tail) {
Node* newHead = nullptr;
try {
newHead = new Node(std::move(data), nullptr);
} catch (...) {
// no changes made
// no leaks
// propogate to caller
throw;
}
tail->mNext = newHead;
}
else {
Node* newHead = nullptr;
try {
newHead = new Node(std::move(data), nullptr);
} catch (...) {
// no changes made
// no leaks
// propogate to caller
throw;
}
mHead = newHead;
}
}
} // Utils
#endif // UTILS_QUEUE
main.cpp
#include <iostream>
#include "queue.h"
using Utils::Queue;
int main()
{
{
Queue<char> charQueue;
charQueue.push_back('a');
char b = 'b';
charQueue.push_back(b);
charQueue.push_back('c');
charQueue.push_back('d');
charQueue.push_back('e');
charQueue.push_back('f');
charQueue.push_back('g');
charQueue.push_back('h');
charQueue.push_back('i');
charQueue.push_back('j');
charQueue.push_back('k');
charQueue.push_back('l');
charQueue.push_back('m');
for (std::size_t i = 0; i < 13; ++i) {
std::cout << charQueue.front() << "\n";
charQueue.pop_front();
}
}
{
Queue<char> a;
a.push_back('a');
a.push_back('b');
a.push_back('c');
Queue<char> b(a);
}
{
Queue<char> a;
a.push_back('a');
a.push_back('b');
a.push_back('c');
Queue<char> b(std::move(a));
}
{
Queue<char> a;
a.push_back('a');
a.push_back('b');
a.push_back('c');
Queue<char> b;
b = a;
}
{
Queue<char> a;
a.push_back('a');
a.push_back('b');
a.push_back('c');
Queue<char> b;
b = std::move(a);
}
{
Queue<char> a;
a.push_back('a');
a.push_back('b');
a.push_back('c');
try {
for (std::size_t i = 0; i < 10; ++i) {
a.front();
a.pop_front();
}
} catch (std::exception& e) {
std::cout << "Caught Exception " << e.what() << "\n";
}
}
std::cin.get();
}
node
is not defined that probably should beother
\$\endgroup\$