I am a mathematician attempting to become proficient with C++. At the moment I am learning about data structures. I am now writing a queue data structure using linked list from scratch.
I have tested my class that I wrote and everything seems to be working fine but I want to see if there are any bugs or some areas of the code I could improve on.
Here is the class:
#ifndef Queue_h
#define Queue_h
template <class T>
class Queue {
private:
    struct Node {
        T data;
        Node* next = nullptr;
    };
    Node* first = nullptr;
    Node* last = nullptr;
    // Used for destructor to delete elements
    void do_unchecked_pop();
    // Use for debugging purposes and for overloading the << operator
    void show(std::ostream &str) const;
public:
    // Constructors
    Queue() = default;                                                        // empty constructor
    Queue(Queue const& value);                                                // copy constructor
    // Rule of 5
    Queue(Queue&& move) noexcept;                                             // move constuctor
    Queue& operator=(Queue&& move) noexcept;                                  // move assignment operator
    ~Queue();                                                                 // destructor
    // Overload operators
    Queue& operator=(Queue const& rhs);
    friend std::ostream& operator<<(std::ostream& str, Queue<T> const& data) {
        data.show(str);
        return str;
    }
    // Member functions
    bool empty() const {return first == nullptr;}
    int size() const;
    T& front() const;
    T& back() const;
    void push(const T& theData);
    void push(T&& theData);
    void pop();
    void swap(Queue& other) noexcept;
};
template <class T>
Queue<T>::Queue(Queue<T> const& value)  {
    try {
        for(auto loop = value.first; loop != nullptr; loop = loop->next)
            push(loop->data);
    }
    catch (...) {
        while(first != nullptr)
            do_unchecked_pop();
        throw;
    }
}
template <class T>
Queue<T>::Queue(Queue&& move) noexcept {
    move.swap(*this);
}
template <class T>
Queue<T>& Queue<T>::operator=(Queue<T> &&move) noexcept {
    move.swap(*this);
    return *this;
}
template <class T>
Queue<T>::~Queue() {
    while(first != nullptr) {
        do_unchecked_pop();
    }
}
template <class T>
int Queue<T>::size() const {
    int size = 0;
    for (auto current = first; current != nullptr; current = current->next)
        size++;
    return size;
}
template <class T>
T& Queue<T>::front() const {
    if(first == nullptr) {
        throw std::out_of_range("the Queue is empty!");
    }
    return first->data;
}
template <class T>
T& Queue<T>::back() const {
    return last->data;
}
template <class T>
void Queue<T>::push(const T& theData) {
    Node* newNode = new Node;
    newNode->data = theData;
    newNode->next = nullptr;
    if(first == nullptr) {
        first = last = newNode;
    }
    else {
        last->next = newNode;
        last = newNode;
    }
}
template <class T>
void Queue<T>::push(T&& theData) {
    Node* newNode = new Node;
    newNode->data = std::move(theData);
    newNode->next = nullptr;
    if(first == nullptr) {
        first = last = newNode;
    }
    else {
        last->next = newNode;
        last = newNode;
    }
}
template <class T>
void Queue<T>::pop() {
    if(first == nullptr) {
        throw std::invalid_argument("the Queue is empty!");
    }
    do_unchecked_pop();
}
template <class T>
void Queue<T>::do_unchecked_pop() {
    Node* tmp = first->next;
    delete tmp;
    first = tmp;
}
template <class T>
void Queue<T>::show(std::ostream &str) const {
    for(Node* loop = first; loop != nullptr; loop = loop->next) {
        str << loop->data << "\t";
    }
    str << "\n";
}
template <class T>
void Queue<T>::swap(Queue<T> &other) noexcept {
    using std::swap;
    swap(first, other.first);
    swap(last, other.last);
}
// Free function
template <typename T>
void swap(Queue<T>& a, Queue<T>& b) noexcept {
    a.swap(b);
}
#endif /* Queue_h */
Here is the main.cpp that tests the latter class:
#include <algorithm>
#include <cassert>
#include <iostream>
#include <ostream>
#include <iosfwd>
#include "Queue.h"
int main(int argc, const char * argv[]) {
      ///////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////// Queue Using Linked List //////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////
    Queue<int> obj;
    obj.push(2);
    obj.push(4);
    obj.push(6);
    obj.push(8);
    obj.push(10);
    std::cout<<"\n--------------------------------------------------\n";
    std::cout<<"---------------Displaying Queue Contents---------------";
    std::cout<<"\n--------------------------------------------------\n";
    std::cout << obj << std::endl;
    std::cout<<"\n--------------------------------------------------\n";
    std::cout<<"---------------Pop Queue Element -------------------";
    std::cout<<"\n--------------------------------------------------\n";
    obj.pop();
    std::cout << obj << std::endl;
    std::cout<<"\n--------------------------------------------------\n";
    std::cout<<"---------------Get the size of Queue -------------------";
    std::cout<<"\n--------------------------------------------------\n";
    std::cout << obj.size() << std::endl;
    std::cout<<"\n--------------------------------------------------\n";
    std::cout<<"---------------Print top element --------------------";
    std::cout<<"\n--------------------------------------------------\n";
    std::cout << obj.front() << std::endl;
    std::cout<<"\n--------------------------------------------------\n";
    std::cout<<"---------------Print last element --------------------";
    std::cout<<"\n--------------------------------------------------\n";
    std::cout << obj.back() << std::endl;
    return 0;
}
