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) {
460 IndexPublisher tail{tail_, currentTail};
461 for (; first != last && count < available; ++first, ++count) {
462 new (elementAt(tail.pos)) T(std::move(*first));
463 tail.pos = increment(tail.pos);
493 template <
typename OutputIt>
495 const size_t currentHead = head_.load(std::memory_order_relaxed);
496 const size_t currentTail = tail_.load(std::memory_order_acquire);
500 if (currentTail >= currentHead) {
501 available = currentTail - currentHead;
503 available = kBufferSize - currentHead + currentTail;
506 if (available == 0) {
511 size_t count = std::min(available, maxCount);
514 IndexPublisher head{head_, currentHead};
515 for (
size_t i = 0; i < count; ++i, ++dest) {
516 T* elem = elementAt(head.pos);
517 *dest = std::move(*elem);
519 head.pos = increment(head.pos);
536 return head_.load(std::memory_order_acquire) == tail_.load(std::memory_order_acquire);
551 return increment(tail_.load(std::memory_order_acquire)) ==
552 head_.load(std::memory_order_acquire);
569 const size_t head = head_.load(std::memory_order_acquire);
570 const size_t tail = tail_.load(std::memory_order_acquire);
572 return (tail >= head) ? (tail - head) : (kBufferSize - head + tail);
588 return kBufferSize - 1;
598 struct IndexPublisher {
599 std::atomic<size_t>& index;
602 IndexPublisher(std::atomic<size_t>& idx,
size_t current)
603 : index(idx), start(current), pos(current) {}
604 IndexPublisher(
const IndexPublisher&) =
delete;
605 IndexPublisher& operator=(
const IndexPublisher&) =
delete;
608 index.store(pos, std::memory_order_release);
619 static constexpr size_t computeBufferSize() noexcept {
620 return RoundUpToPowerOfTwo ?
static_cast<size_t>(detail::nextPow2(Capacity + 1)) : Capacity + 1;
630 static constexpr size_t kBufferSize = computeBufferSize();
637 static constexpr bool kIsPowerOfTwo = (kBufferSize & (kBufferSize - 1)) == 0;
644 static constexpr size_t kMask = kBufferSize - 1;
655 static constexpr size_t increment(
size_t index)
noexcept {
656 return kIsPowerOfTwo ? ((index + 1) & kMask) : ((index + 1) % kBufferSize);
662 T* elementAt(
size_t index) {
663 return reinterpret_cast<T*
>(&storage_[index *
sizeof(T)]);
666 const T* elementAt(
size_t index)
const {
667 return reinterpret_cast<const T*
>(&storage_[index *
sizeof(T)]);
678 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.