I have written a simple fixed size queue and am looking to get feedback on best practices. Specifically, handling unintended behaviors e.g. construct with negative size. This is not for a specific project but more of practicing good coding skills. I tried to follow the Google Code Style.
I am also looking for feedback on code coverage and unit tests.
Note: I realize that the pop function that I implemented is different that the std::queue and I don't have front and back functions.
Full code including CMakeLists.txt is here
fixed_size_queue.h
#ifndef FIXED_SIZE_QUEUE_H
#define FIXED_SIZE_QUEUE_H
#include "iostream"
template <class T> class FixedSizeQueue {
private:
int size_;
int start_index_;
int end_index_;
T *start_p_;
public:
// Must be constructed with an argument
FixedSizeQueue(int size);
~FixedSizeQueue();
bool isEmpty() const;
bool isFull() const;
// push a single element to the queue
// returns false if queue is full
// returns true otherwise
bool push(const T& data);
// pop a single element from the queue
// returns the element on success
// throws a logic_error exception if attempting to pop from an empty queue (undefined behavior)
T pop();
void print() const;
};
#endif
fixed_size_queue.cpp
#include "fixed_size_queue.h"
#include "gmock/gmock.h"
#include "stdexcept"
template <class T>
FixedSizeQueue<T>::FixedSizeQueue(int size)
: size_(size), start_index_(-1), end_index_(-1), start_p_(new T[size]) {}
template <class T> FixedSizeQueue<T>::~FixedSizeQueue() { delete[] start_p_; }
template <class T> bool FixedSizeQueue<T>::isEmpty() const {
return start_index_ == -1;
}
template <class T> bool FixedSizeQueue<T>::isFull() const {
if ((end_index_ + 1 == start_index_) ||
(start_index_ == 0 && end_index_ == size_ - 1))
return true;
else
return false;
}
template <class T> bool FixedSizeQueue<T>::push(const T &data) {
if (isFull()) {
return false;
} else if (isEmpty()) {
start_index_ = 0;
end_index_ = 0;
start_p_[end_index_] = data;
} else if (end_index_ != size_ - 1) {
start_p_[++end_index_] = data;
} else {
end_index_ = 0;
start_p_[end_index_] = data;
}
return true;
}
template <class T> T FixedSizeQueue<T>::pop() {
if (isEmpty()) {
throw std::logic_error("Empty Queue");
} else if (start_index_ == end_index_) {
T temp = start_p_[end_index_];
start_index_ = -1;
end_index_ = -1;
return temp;
} else if (start_index_ == size_ - 1) {
start_index_ = 0;
return start_p_[size_ - 1];
} else {
return start_p_[start_index_++];
}
}
template <class T> void FixedSizeQueue<T>::print() const {
if (start_index_ == -1) {
std::cout << "Empty Queue" << std::endl;
} else {
for (int i = start_index_; i != end_index_;) {
std::cout << start_p_[i] << "\t";
if (i == size_ - 1) {
i = 0;
} else {
++i;
}
}
std::cout << start_p_[end_index_];
std::cout << std::endl;
}
}
TEST(QueueTest, PushAndPopWithoutWrapping) {
FixedSizeQueue<int> queue(5);
queue.push(1);
queue.push(2);
ASSERT_THAT(queue.pop(), testing::Eq(1));
ASSERT_THAT(queue.pop(), testing::Eq(2));
}
TEST(QueueTest, PushAndPopWithWrapping) {
FixedSizeQueue<int> queue(3);
queue.push(1);
queue.push(2);
queue.push(3);
queue.pop();
queue.push(4);
ASSERT_THAT(queue.pop(), testing::Eq(2));
ASSERT_THAT(queue.pop(), testing::Eq(3));
ASSERT_THAT(queue.pop(), testing::Eq(4));
}
TEST(QueueTest, PushToAFullQueue) {
FixedSizeQueue<int> queue(3);
ASSERT_THAT(queue.push(1), testing::Eq(true));
ASSERT_THAT(queue.push(2), testing::Eq(true));
ASSERT_THAT(queue.push(3), testing::Eq(true));
ASSERT_THAT(queue.push(4), testing::Eq(false));
}
TEST(QueueTest, PopFromEmptyQueue) {
FixedSizeQueue<int> queue(3);
try {
queue.pop();
} catch (std::logic_error &e) {
EXPECT_STREQ("Empty Queue", e.what());
}
}
unsigned
, or (even better)size_t
. FixedSizeQueue(-1) does really bad things. \$\endgroup\$size_
to be of type size_t_ but I wantstart_index_
to remain an int so that I can assign -1 to it when the queue is empty. This causes a warning when doingstart_index_ == size_ -1
comparison between signed and unsigned integer expressions. I can dostart_index_ == (int)size_ -1
instead. Can you recommend a better way? of course going to astd::vector
implementation as suggested below will solve this altogether. \$\endgroup\$unsigned count
(current fill level) instead ofend_index
? BothisEmpty
andisFull
implementations would become easier, but you would have to replaceend_index
withstart_index+count
... \$\endgroup\$