It's been nearly a year I've been using C++, and I have just implemented this queue. I've tested it in many different scenarios, and it seems to work completely fine. Would you mind telling me what can be improved? What techniques might and might not be used in a professional scenario? In general, could you please give me your opinions so that I can improve my skills?
// --- Implementation of an exception class
class E: public std::exception{
const char * _msg = "Default Exception.";
E(){};
public:
E(const char * message) throw() { this->_msg = message; }
const char * what() const throw(){ return this->_msg; }
};
// --- Implementation of a queue.
template <typename T> class Queue {
static const int _defaultSize = 10;
static const int _maxSize = 1000;
int _size;
int _currentSize = 0;
T * _queuePointer;
int _firstInQueue = -1; // Holds the index of the first item in the queue (the item that should be popped).
int _lastIndex = -1; // Holds the index that we have just pushed a new element to. ("last index to have an element being added to")
public:
// Constructors and Destructors
Queue(int sz=_defaultSize); // Default/Int Constructor Constructor
Queue(const Queue & other); // Copy Constructor
~Queue(); // Destructor
// Overloaded Assignment Operator
Queue & operator = (const Queue rhs); // To implement the copy-and-swap idiom
// Utility Functions
void swap(Queue & rhs);
T enqueue(const T & node);
T dequeue();
bool isFull() const { return (this->getCurrentSize() == this->getSize()); };
bool isEmpty() const { return (!this->getCurrentSize()); };
// Getters/Accessors
int getCurrentSize() const { return this->_currentSize; }
int getSize() const { return this->_size; }
};
// Implementation of Constructors and Destructors
template <typename T> Queue<T>::Queue(int sz){
if (sz < 1 || sz > _maxSize){
// Invalid 'sz' argument value.
throw E("Queue Exception: Invalid size argument value.");
}else {
std::cout << "Created Object (Default/Int Constructor)" << std::endl;
this->_size = sz;
this->_queuePointer = new T[this->_size];
}
}
template <typename T> Queue<T>::Queue(const Queue<T> & other){
this->_size = other._size;
this->_currentSize = other._currentSize;
this->_lastIndex = other._lastIndex;
this->_queuePointer = new T[this->_size];
for(int i=0; i < this->_size; i++){
this->_queuePointer[i] = other._queuePointer[i];
}
}
template <typename T> Queue<T>::~Queue(){
delete [] this->_queuePointer;
}
// Implementation Of The Overloaded Assignment Operator
template <typename T> Queue<T> & Queue<T>::operator = (Queue<T> rhs){ // So that I can use the copy-and-swap idiom.
this->swap(rhs);
return *this;
}
// Implementation of Utility Functions
template <typename T> void Queue<T>::swap(Queue<T> & rhs){
std::swap(this->_size, rhs._size);
std::swap(this->_currentSize, rhs._currentSize);
std::swap(this->_lastIndex, rhs._lastIndex);
/*
As I am assigning, it means that dynamic memory was
allocated for the lhs object. So, before copying the
content of the rhs to the lhs object, let's delete
the allocated memory from the lhs object and allocate
again based on the new size.
*/
delete [] this->_queuePointer;
this->_queuePointer = new T[this->_size];
for(int i=0; i < this->_size; i++){
this->_queuePointer[i] = rhs._queuePointer[i];
}
}
template <typename T> T Queue<T>::enqueue(const T & node){
if(this->isFull()){
// The queue is full.
throw E("Queue Exception: Your queue is full! You can't push anymore until you pop something.");
}else {
// The queue is not full.
if(this->_firstInQueue == -1){
// If it is the first item being pushed to the queue.
this->_firstInQueue++; // The first in queue is now index 0.
}
// This if statement will just be executed if I push another node, and the last
// node I added was at the last position of the queue.
if(this->_lastIndex == (this->getSize() - 1)){
// If the last index is at the last position of the queue,
// set the last index to -1 again.
this->_lastIndex = -1;
}
// Increasing index to the index number that we should add the new element.
this->_lastIndex++;
// Pushing element here (with respect to/using lastindex)...
this->_queuePointer[this->_lastIndex] = node;
// Increasing the current size of the queue.
this->_currentSize++;
}
return (this->_queuePointer[this->_lastIndex]);
}
template <typename T> T Queue<T>::dequeue(){
if(this->isEmpty()){
// The queue is empty.
throw E("Queue Exception: Your queue is empty. There is nothing to pop!");
}
// The queue is not empty.
T value_to_be_returned = this->_queuePointer[this->_firstInQueue];
if(this->_currentSize == 1){
// If the queue has just one element and this element is at index 0.
// Setting the index of the first in the queue to -1, because I am popping the
// last element of the queue. Now, if I push a new element after popping this last one,
// the element being pushed will be first in the queue, and its index will be 0.
this->_firstInQueue = -1; // The first in queue is now back to index -1.
// Returning the last index to -1, so that when the new item is pushed, the last index will be 0.
this->_lastIndex = -1;
// OBS: fiq and the li must ALWAYS go back to their initial values, -1, if all
// the values are popped from the queue.
}else {
// Increasing index.
// This if statement will just be executed if the first element in the queue
// is at the last position of the queue. If so, we need to set the first in queue
// variable to -1 again, and then increase it to 0, so that the next element first
// in the queue is at index 0.
if (this->_firstInQueue == (this->getSize() - 1)){
this->_firstInQueue = -1;
}
this->_firstInQueue++;
}
// Decreasing queue's current size.
this->_currentSize--;
return value_to_be_returned;
}