我使用数组实现了一个队列,并将其视为循环缓冲区。_tail指向要读取的下一个元素,_head指向要写入的下一个元素:
template<int SIZE>
class Q {
bool push(const int item){
if(false == isFull()){
_q[_head] = item;
_head = ++_head % SIZE;
return true;
}
return false;
}
bool pop(int& toReturn){
if(false == isEmpty()){
toReturn = _q[_tail];
_tail = ++_tail % SIZE;
return true;
}
return false;
}
private:
std::array<int, SIZE> _q;
std::atomic<int> _head;
std::atomic<int> _tail;
};但是,我的isFull()和isEmpty()逻辑中有一个错误,通过以下问题确定:
当队列最初为空时,_tail和_head都将指向_q[0]。
当我编写了_q[0]、_q[1]、_q[2]和q[3]之后,_tail和_head将再次指向相同的内容,所以很明显我不能使用_tail == _head来确定full/empty。
实现isEmpty()和isFull()逻辑的最简单方法是什么?我不确定是否应该在读取队列项之后编写一个“空白”枚举?
发布于 2021-12-26 04:26:43
有很多关于循环缓冲区的虚假信息。首先,这是一个双人赛道。每当你看到2个指针或索引时,你可能正在处理一个双端队列,你会看到头部和尾部,以及与循环缓冲区相关的频繁读和写指针。但这都是错的,循环缓冲区是一个双队列,因此与双链表相关,后者也可以用作双队列。因此,您应该考虑的是第一个和最后一个节点,而不是头和尾,就像使用双向链表一样。然后,您可以选择first == last表示空,next(last) == first表示full。或者,first == last && first == nullptr代表empty,first == last && first != nullptr代表full,但这会使代码变得复杂。我们通常不关心丢失一个插槽,我们只关心性能。
还有第三种选择,这是有学术头脑的人更喜欢的。你可以有一个size成员,size == 0代表empty,size == capacity代表full。在这种情况下,first像以前一样指向第一个占用的时隙,但last指向最后一个占用的时隙,而不是刚刚超过的那个。
最重要的是查看有关deque的文献,因为不同的人倾向于以不同的方式实现他们的循环缓冲区(例如,头部和尾部经常交换角色),deque通常更多地在学术上处理,毫不含糊。
https://stackoverflow.com/questions/43805858
复制相似问题