4
\$\begingroup\$

I'm currently doing a project, requiring me to implement a CircularQueue data structure in C++:

template<typename Type> class CircularQueue {

template<typename Type> struct CQNode {
    Type head;
    CQNode* tail;

    CQNode() {
        this->head = NULL;
        this->tail = NULL;
    }

    CQNode(Type head, CQNode* tail = NULL) {
        this->head = head;
        this->tail = tail;
    }

};


private:

CQNode<Type>* front;
CQNode<Type>* rear;
CQNode<Type>** circularQueue = NULL;
int MAX = 0;
int currentSize = 0;


protected:

public:

/**********************************************************/
/********************** CONSTRUCTORS **********************/
/**********************************************************/

/**
 * @brief
 * 
 * [DETAILED DESCRIPTION]
 *
 * @param capacity integer size capacity of CircularQueue object to be instantiated
 */
CircularQueue(int capacity) {

    // Set the maximum queue capacity to the size passed to constructor
    this->MAX = capacity;

    // Locally set the limit (to be used as array of CQNode size) to MAX
    const int limit = this->MAX;

    // Create a pointer array of CQNode with size limit and assign to this->circularQueue
    this->circularQueue = new CQNode<Type>*[limit];

    // Loop over queue capacity, implementing CircularQueue type structure
    for (int i = 0; i < limit; i++) {

        // set i'th element of circularQueue array to a new CQNode struct
        this->circularQueue[i] = new CQNode<Type>();

        // if the value of i is greater than 0, assign the tail of the (i-1)'th element 
        // of circularQueue to i'th element of circularQueue, thereby implementing the Queue
        // FIFO type data structure
        if (i > 0) {
            this->circularQueue[i - 1] = this->circularQueue[i];
        }

        // otherwise, if i is equal to the capacity - 1 (i.e. the last elementt of circularQueue array),
        // then set the tail of the last element of circularQueue to the first element of circularQueue,
        // thereby implementing the circular nature of this queue data structure
        else if (i == (limit - 1)) {
            this->circularQueue[i - 1]->tail = this->circularQueue[0];
        }

    }

    this->front = NULL;
    this->rear = NULL;
}

/**********************************************************/
/********************** DESTRUCTORS ***********************/
/**********************************************************/

/**
 * @brief
 *
 * [DETAILED DESCRIPTION]
 *
 */
~CircularQueue() {
    //TODO: Destroy Queue
}

/********************************************************/
/*********************** GETTERS ************************/
/********************************************************/

CQNode<Type>* peekFront() {
    return this->front;
}

CQNode<Type>* peekRear() {
    return this->rear;
}

Type peekHeadOfFront() {

    if (this->front != NULL)
        return this->front->head;

}

int getCurrentSize() {
    return this->currentSize;
}

bool isEmpty() {
    return this->rear == NULL;
}

/**********************************************/
/****************** TOSTRING ******************/
/**********************************************/

/**
 * @brief
 *
 * [DETAILED DESCRIPTION]
 *
 * @return string representation of this CircularQueue object
 */
string toString() {

    string temp = "[";

    CQNode<Type>* tempNode = this->front;

    int i = 0;

    while (tempNode != NULL && i < this->currentSize) {
        i++;
        //cout << temp << endl;
        if (tempNode->tail != NULL) {
            temp += std::to_string(tempNode->head) + ", ";
        }
        else {
            temp += std::to_string(tempNode->head);
        }
        tempNode = tempNode->tail;
    }

    temp += "]";

    return temp;

}

/********************************************************/
/******************* QUEUE OPERATIONS *******************/
/********************************************************/

/**
 * @brief
 *
 * [DETAILED DESCRIPTION]
 *
 * @param item Object to enqueue to this instance of CircularQueue
 * @return The item of data structure "Type" enqueued upon this CircularQueue instance
 */
Type enqueue(Type item) {

    // if the currentSize is greater than the maximum allowed capacity,
    // throw a CircularQueueException
    if (this->currentSize == this->MAX) {
        throw CircularQueueException("Circular queue is full, cannot enqueue any more objects!");
    }

    // if the front of this CQ object is null, assign first element of circularQueue array to 
    // front of queue and set the rear to the front (single-element queue)
    if (this->front == NULL) {
        //this->front = new CQNode<Type>(item);
        this->front = this->circularQueue[0];
        this->rear = this->front;
    }

    // else if the front is not-null, assign the tail of the rear of this CQ object
    // to a new CQNode with head = item, and shift the new rear to tail of old rear
    else {
        this->rear->tail = new CQNode<Type>(item);

        this->rear = this->rear->tail;

        if (this->currentSize == (this->MAX - 1))
            this->rear->tail = this->front;

    }

    this->currentSize++;

    return item;

}

/**
 * @brief
 *
 * [DETAILED DESCRIPTION]
 *
 * @return The item of data structure "Type" dequeued from this CircularQueue instance
 */
Type dequeue() {

    // Create a pointer to a CQNode initialised as NULL
    // this variavle will store the dequeued node
    CQNode<Type>* dequeuedNode = NULL;

    // if rear is empty, throw a CircularQueueException
    if (this->rear == NULL) {
        throw CircularQueueException("Circular queue is already empty, cannot dequeue any objects!");
    }

    else {

        // decrement currentSize of this CircularQueue instance by 1
        this->currentSize--;

        // assign front of this CircularQueue to dequeuedNode
        dequeuedNode = this->front;

        // set the new front of the queue to the tail of the current front
        this->front = this->front->tail;

    }

    return dequeuedNode->head;
}


};

Is this a good implementation for such a structure so far? Please mention any bad coding practices or potential problems that you can see with this code.

Note that my toString() function is designed as to only print a single "circle" of the queue, if there is a better way to make a toString() for a CircularQueue then please mention it.

\$\endgroup\$
3
  • \$\begingroup\$ Why do you create a linked list inside of an array instead of using just the array? \$\endgroup\$ Commented Nov 1, 2015 at 18:02
  • \$\begingroup\$ Welcome to Code Review! Good job on your first question! \$\endgroup\$ Commented Nov 1, 2015 at 18:05
  • \$\begingroup\$ @Zulan Good question, I guess that's a layer of abstraction that I can remove \$\endgroup\$ Commented Nov 1, 2015 at 18:46

3 Answers 3

4
\$\begingroup\$

A few other points:

  • Since you have defined everything inline inside the class declaration, it would be nice to add one level of indentation to everything inside the class, otherwise the methods don't look like they belong to a class.

  • MAX is not a great name. Actually, the constructor assigns a capacity variable to it, so why not rename MAX to capacity and clearly indicate that this is the maximum capacity of the backing array? Also, if you properly initialize your member data in the constructor initializer list, you can declare capacity (or former MAX) as a constant (const int capacity;), which is nice, since it doesn't seem like you've devised this data structure to ever change capacity after construction. Quick example:

    // This is what a constructor initializer list looks like.
    // It is a comma separated list of members. This is unique to C++
    // and the recommended way of initializing member data in a constructor.
    CircularQueue(int cap)
        : capacity(cap)
        , currentSize(0)
        , circularQueue(new CQNode<Type>*[capacity])
    {
        // stuff ...
    }
    
  • this-> qualifying member access should be avoided. It's not mandatory as it is in some other languages, so it's only adding verbosity to your code. But also, this qualifying member access can hide problems of name shadowing, which is a very bad practice. Each entity should have a unique name.

  • Prefer using nullptr instead of NULL. Very likely that your compiler is C++11 compliant. If not, see to update it, there are several free alternatives out there. nullptr is the default null pointer literal for C++11 and above.

  • As mentioned by @Zulan, declaring a template class inside a template class with the same parameter name for the template is incorrect (i.e.: template<typename Type> for both CircularQueue and CQNode). If I recall, the Visual Studio compiler did allow this kind of malformed code in the past, so perhaps that's why it is working for you... You don't have to make CQNode a template. It can directly access the parent Type from CircularQueue, so this is an easy fix.

  • protected section of your class is empty, so you can remove that tag.

  • Those header comments are way too verbose. Unless you really have a requirement for that kind of mechanical code commenting, don't do it. It's just distracting from the actual code.

  • You never freed memory in the destructor, which would be the place for it, so your class is leaking resources. However, in modern C++ it is rare to manually manage memory like that. The standard library offers containers meant to keep track of mechanical and error-prone resource management for you. You can either use a std::vector for the node backing store or at the very least a std::unique_ptr with array storage, to make cleanup automated.

  • Methods that don't modify member data should be const to allow them to be called on a const object and to also clearly signify that to readers. You can learn more about const member functions in here.

  • A huge architectural improvement and code cleanup would come from dropping this linked list setup and using a plain array. Since the queue is fixed size, you can very easily implement the ring buffer with an array of values and a head and tail indexes. This will make your code much simpler and more efficient, since you'll get rid of a ton of pointer indirections.

\$\endgroup\$
1
  • \$\begingroup\$ Good advice, thanks. The compiler I must use for this project is ancient (not C++ 11 compliant) - pretty unacceptable for 2015 but there's nothing I can do about it unfortunately; if there was then I would've done it already. As for dropping the linked list implementation, I would prefer not to as there is scope for me to extend the functionality of this class to dynamic size - also coming from mostly Java developing in the last year, this linked list structure makes more sense to me than a purely array based implementation; even if the latter would clean-up the code a bit. \$\endgroup\$ Commented Nov 3, 2015 at 21:09
2
\$\begingroup\$

Bug

This code in the constructor:

    if (i > 0) {
        this->circularQueue[i - 1] = this->circularQueue[i];
    }

should have been:

    if (i > 0) {
        this->circularQueue[i - 1]->tail = this->circularQueue[i];
    }

This makes me feel like you didn't test your code at all.

\$\endgroup\$
1
\$\begingroup\$

Your code does not compile for a number of reasons.

  1. The template parameters Type shadow each other
  2. NULL is not defined. Use nullptr, or if you really cannot use C++11 0. NULL is defined in in a couple of c... headers.
  3. You do not define CircularQueueException.
  4. You use string, without std::. DO NOT try to fix this by adding using namespace std;.

Further you must implement the destructor in order to get a sensible review of your code.

Please provide a fully working example.

I fail to understand the Idea behind building a linked list inside of an array. It is probably a bad idea unless I overlook something fundamental.

Also I get really upset at comments like those:

/**
 * @brief
 *
 * [DETAILED DESCRIPTION]
 *
 */

Please do not waste space without providing any kind of information.

I would suggest you address these issues and then go for a follow-up question.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.