110template <
typename T,
size_t Capacity = 16,
bool RoundUpToPowerOfTwo = true>
112 static_assert(Capacity >= 1,
"SPSCRingBuffer capacity must be at least 1");
114 std::is_move_constructible<T>::value,
115 "SPSCRingBuffer element type must be move-constructible");
121 using value_type = T;
126 using size_type = size_t;
180 size_t head = head_.load(std::memory_order_relaxed);
181 size_t tail = tail_.load(std::memory_order_relaxed);
182 while (head != tail) {
183 elementAt(head)->~T();
184 head = increment(head);
213 const size_t currentTail = tail_.load(std::memory_order_relaxed);
214 const size_t nextTail = increment(currentTail);
217 if (nextTail == head_.load(std::memory_order_acquire)) {
222 new (elementAt(currentTail)) T(std::move(item));
223 tail_.store(nextTail, std::memory_order_release);
242 const size_t currentTail = tail_.load(std::memory_order_relaxed);
243 const size_t nextTail = increment(currentTail);
246 if (nextTail == head_.load(std::memory_order_acquire)) {
251 new (elementAt(currentTail)) T(item);
252 tail_.store(nextTail, std::memory_order_release);
277 template <
typename... Args>
279 const size_t currentTail = tail_.load(std::memory_order_relaxed);
280 const size_t nextTail = increment(currentTail);
283 if (nextTail == head_.load(std::memory_order_acquire)) {
288 new (elementAt(currentTail)) T(std::forward<Args>(args)...);
289 tail_.store(nextTail, std::memory_order_release);
318 const size_t currentHead = head_.load(std::memory_order_relaxed);
321 if (currentHead == tail_.load(std::memory_order_acquire)) {
325 T* elem = elementAt(currentHead);
326 item = std::move(*elem);
328 head_.store(increment(currentHead), std::memory_order_release);
359 const size_t currentHead = head_.load(std::memory_order_relaxed);
362 if (currentHead == tail_.load(std::memory_order_acquire)) {
366 T* elem = elementAt(currentHead);
367 OpResult<T> result(std::move(*elem));
369 head_.store(increment(currentHead), std::memory_order_release);
399 const size_t currentHead = head_.load(std::memory_order_relaxed);
402 if (currentHead == tail_.load(std::memory_order_acquire)) {
406 T* elem = elementAt(currentHead);
407 new (storage) T(std::move(*elem));
409 head_.store(increment(currentHead), std::memory_order_release);
437 template <
typename InputIt>
439 const size_t currentTail = tail_.load(std::memory_order_relaxed);
440 const size_t currentHead = head_.load(std::memory_order_acquire);
444 if (currentTail >= currentHead) {
446 available = (kBufferSize - 1) - (currentTail - currentHead);
449 available = currentHead - currentTail - 1;
452 if (available == 0) {
458 size_t tailPos = currentTail;
459 for (; first != last && count < available; ++first, ++count) {
460 new (elementAt(tailPos)) T(std::move(*first));
461 tailPos = increment(tailPos);
465 tail_.store(tailPos, std::memory_order_release);
495 template <
typename OutputIt>
497 const size_t currentHead = head_.load(std::memory_order_relaxed);
498 const size_t currentTail = tail_.load(std::memory_order_acquire);
502 if (currentTail >= currentHead) {
503 available = currentTail - currentHead;
505 available = kBufferSize - currentHead + currentTail;
508 if (available == 0) {
513 size_t count = std::min(available, maxCount);
514 size_t headPos = currentHead;
515 for (
size_t i = 0; i < count; ++i, ++dest) {
516 T* elem = elementAt(headPos);
517 *dest = std::move(*elem);
519 headPos = increment(headPos);
523 head_.store(headPos, std::memory_order_release);
540 return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire);
555 return increment(tail_.load(std::memory_order_acquire)) ==
556 head_.load(std::memory_order_acquire);
573 const size_t head = head_.load(std::memory_order_acquire);
574 const size_t tail = tail_.load(std::memory_order_acquire);
576 return (tail >= head) ? (tail - head) : (kBufferSize - head + tail);
592 return kBufferSize - 1;
602 static constexpr size_t computeBufferSize() noexcept {
603 return RoundUpToPowerOfTwo ?
static_cast<size_t>(detail::nextPow2(Capacity + 1)) : Capacity + 1;
613 static constexpr size_t kBufferSize = computeBufferSize();
620 static constexpr bool kIsPowerOfTwo = (kBufferSize & (kBufferSize - 1)) == 0;
627 static constexpr size_t kMask = kBufferSize - 1;
638 static constexpr size_t increment(
size_t index)
noexcept {
639 return kIsPowerOfTwo ? ((index + 1) & kMask) : ((index + 1) % kBufferSize);
645 T* elementAt(
size_t index) {
646 return reinterpret_cast<T*
>(&storage_[index *
sizeof(T)]);
649 const T* elementAt(
size_t index)
const {
650 return reinterpret_cast<const T*
>(&storage_[index *
sizeof(T)]);
661 alignas(T)
char storage_[
sizeof(T) * kBufferSize];
A lock-free single-producer single-consumer ring buffer with fixed capacity.
size_type try_push_batch(InputIt first, InputIt last)
Attempts to push multiple elements into the buffer.
bool try_pop_into(T *storage)
Attempts to pop an element into uninitialized storage.
bool try_push(const T &item)
Attempts to push an element into the buffer by copying.
SPSCRingBuffer(SPSCRingBuffer &&)=delete
Ring buffers are not movable.
bool try_push(T &&item)
Attempts to push an element into the buffer by moving.
~SPSCRingBuffer()
Destroys the ring buffer.
SPSCRingBuffer()=default
Constructs an empty ring buffer.
bool try_emplace(Args &&... args)
Attempts to construct an element in-place in the buffer.
static constexpr size_type capacity() noexcept
Returns the maximum number of elements the buffer can hold.
bool try_pop(T &item)
Attempts to pop an element from the buffer.
size_type size() const
Returns the current number of elements in the buffer.
size_type try_pop_batch(OutputIt dest, size_type maxCount)
Attempts to pop multiple elements from the buffer.
OpResult< T > try_pop()
Attempts to pop an element from the buffer, returning an optional.
SPSCRingBuffer(const SPSCRingBuffer &)=delete
Ring buffers are not copyable.
bool empty() const
Checks if the buffer is empty.
bool full() const
Checks if the buffer is full.