检测环形缓冲区的无效迭代器
detecting invalid iterators for a ring buffer
我正在尝试实现一个环形缓冲区(或循环缓冲区)。与这些实现中的大多数一样,它应该尽可能快、尽可能轻,但仍然提供足够的安全性,足以用于生产。这是一个很难达成的平衡。我尤其面临以下问题。
我想使用所述缓冲区来存储最后n个系统事件。随着新事件的出现,最旧的事件将被删除。然后,我的软件的其他部分可以访问这些存储的事件,并按照自己的节奏进行处理。一些系统可能会以事件到达的速度消耗事件,而另一些系统可能只是偶尔检查。每个系统都会在缓冲区中存储一个迭代器,这样他们就知道上次检查时停在了哪里。只要他们检查得足够频繁,这就没有问题,但尤其是速度较慢的系统可能经常发现自己有一个旧的迭代器,它指向一个缓冲区元素,该元素后来被覆盖,却没有办法检测到。
有没有一种好的(不太昂贵的)方法来检查任何给定的迭代器是否仍然有效?
到目前为止我想到的东西:
- 保留所有迭代器的列表并存储它们的有效状态(相当昂贵)
- 不仅在调用系统中存储迭代器,而且在缓冲区的客户端中存储指向元素的副本。在每次访问时,检查元素是否仍然相同。这可能是不可靠的。如果元素已被相同的元素覆盖,则无法检查它是否已更改。此外,找到检查元素的好方法的责任在于客户,这在我看来并不理想
许多环形缓冲区实现根本不关心这一点,或者使用单读单写习惯用法,即读取即删除。
不存储值,而是存储(value, sequence_num)
对。推送新的value
时,请始终确保它使用不同的sequence_num
。对于sequence_num
,可以使用单调递增的整数。
然后,迭代器会记住它最后一次查看的元素的sequence_num
。如果不匹配,它就会被覆盖。
我同意Roger Lipscombe的观点,使用序列号。
但您不需要存储(value,sequence_num)对:只需存储值,并跟踪到目前为止的最高序列号。由于它是一个环形缓冲区,您可以推断出所有条目的seqnum。
因此,迭代器只是由一个序列号组成。
假设Obj
是您存储在环形缓冲区中的对象类型,如果您使用一个简单的数组,您的环形缓冲区将如下所示:
struct RingBuffer {
Obj buf[ RINGBUFFER_SIZE ] ;
size_t idx_last_element ;
uint32_t seqnum_last_element ;
void Append( const Obj& obj ) { // TODO: Locking needed if multithreaded
if ( idx_last_element == RINGBUFFER_SIZE - 1 )
idx_last_element = 0 ;
else
++idx_last_element ;
buf[ idx_last_element ] = obj ; // copy.
++ seqnum_last_element ;
}
}
迭代器看起来是这样的:
struct RingBufferIterator {
const RingBuffer* ringbuf ;
uint32_t seqnum ;
bool IsValid() {
return ringbuf &&
seqnum <= ringbuf->seqnum_last_element &&
seqnum > ringbuf->seqnum_last_element - RINGBUFFER_SIZE ; //TODO: handle seqnum rollover.
}
Obj* ToPointer() {
if ( ! IsValid() ) return NULL ;
size_t idx = ringbuf->idx_last_element - (ringbuf->seqnum_last_element-seqnum) ; //TODO: handle seqnum rollover.
// handle wrap around:
if ( idx < 0 ) return ringbuf->buf + RINGBUFFER_SIZE- idx ;
return ringbuf->buf + idx ;
}
}
Roger Lipscombe答案的一个变体是使用序列号作为迭代器。序列号应该是单调递增的(当整数类型溢出时要特别小心),有一个固定的步长(例如1)
循环缓冲区本身将正常存储数据,并跟踪其当前包含的最旧序列号(位于尾部位置)。
当取消引用迭代器时,迭代器的序列号将与缓冲区最旧的序列号进行检查。如果它大于或等于(同样要特别注意整数溢出),则可以使用简单的索引计算来检索数据。如果它较小,则意味着数据已被覆盖,而应该检索当前的尾部数据(相应地更新迭代器的序列号)。
- 修改 std::vector 会使迭代器无效吗?
- 二进制表达式的操作数无效 - 使用 for 和迭代器
- 无效迭代器上的算术
- 插入 boost::multi_index 后迭代器变得无效?
- 在擦除或修改作为不同索引键的值时,boost::multi_index 迭代器是否无效?
- 如果迭代器没有因插入而无效,则使用std::find和C::insert()是线程安全的
- 如何获得保证的无效迭代器(用于向量)
- 在这种情况下,可以比较无效的迭代器
- 为什么映射迭代器显示基本操作数无效错误
- 在向量中无效的迭代器
- 迭代器擦除矢量中的元素无效,但它不会崩溃
- 我可以在 std::list 中移动元素而不会使迭代器或引用无效,但是如何移动呢?
- 如果迭代器的迭代器永远不会无效,则是STD :: MAP访问线程安全
- 如何避免使用“ReplaceInstWithValue()”使迭代器无效
- 当此容器也是容器的元素时,为什么容器的迭代器无效
- std :: move()会使迭代器无效
- 无效迭代器:如何在第一次使用时获取一些调试信息
- 检测环形缓冲区的无效迭代器
- 哪些操作为无效迭代器定义
- 在解引用无效迭代器时强制异常