#pragma once #include #include #include #include class RingBufferSentinel {}; template class RingBufferIterator { public: using difference_type = TContainer::difference_type; using value_type = TContainer::value_type; // C++20 relaxed usage requirements of `typename`, now locations where a type is required (like here in a using statement) it's no longer mandatory using pointer = value_type*; using reference = value_type&; using iterator_category = std::random_access_iterator_tag; public: TContainer* container; TContainer::size_type curr; // C++20 relaxed usage requirements of `typename`, same here bool needsWrapAround; bool hasWrappedAround = false; public: reference operator*() const { return container->mRing[curr]; } RingBufferIterator& operator++() { assert(*this != RingBufferSentinel{}); ++curr; if (needsWrapAround && curr == container->mCapacity) { hasWrappedAround = true; curr = 0; } return *this; } bool operator==(const RingBufferIterator& that) const { assert(this->container == that.container); return this->curr == that.curr; } bool operator==(const RingBufferSentinel&) const { return curr == container->mTailIdx && (!needsWrapAround || hasWrappedAround); } }; template class RingBuffer { public: using value_type = T; using reference = T&; using const_reference = const T&; friend class RingBufferIterator; using iterator = RingBufferIterator; friend class RingBufferIterator; using const_iterator = RingBufferIterator; using sentinel = RingBufferSentinel; // Not a part of C++'s Container named requirements, added here for convenience using difference_type = ptrdiff_t; using size_type = size_t; private: T* mRing = nullptr; size_type mHeadIdx = 0; size_type mTailIdx = 0; size_type mCapacity = 0; size_type mSize = 0; public: RingBuffer() noexcept = default; ~RingBuffer() noexcept { delete mRing; } RingBuffer(const RingBuffer&) noexcept = delete; RingBuffer& operator=(const RingBuffer&) noexcept = delete; RingBuffer(RingBuffer&& that) noexcept : mRing{ that.mRing } , mHeadIdx{ that.mHeadIdx } , mTailIdx{ that.mTailIdx } , mCapacity{ that.mCapacity } , mSize{ that.mSize } { that.mRing = nullptr; } RingBuffer& operator=(RingBuffer&& that) noexcept { if (this != &that) { auto oldThisRing = this->mRing; this->mRing = that.mRing; that.mRing = nullptr; delete oldThisRing; this->mHeadIdx = that.mHeadIdx; this->mTailIdx = that.mTailIdx; this->mCapacity = that.mCapacity; this->mSize = that.mSize; } return *this; } [[nodiscard]] iterator begin() { return { .container = this, .curr = mHeadIdx, .needsWrapAround = mHeadIdx >= mTailIdx, }; } [[nodiscard]] const_iterator begin() const { return cbegin(); } // Same type for both const this and non-const `this` [[nodiscard]] sentinel end() const { return sentinel{}; } [[nodiscard]] const_iterator cbegin() const { return { .container = this, .curr = mHeadIdx, .needsWrapAround = mHeadIdx >= mTailIdx, }; } [[nodiscard]] sentinel cend() const { return sentinel{}; } [[nodiscard]] T& operator[](size_type i) { return const_cast(const_cast(*this)[i]); } [[nodiscard]] const T& operator[](size_type i) const { size_type idx = mHeadIdx + i; if (idx >= mCapacity) { idx -= mCapacity; } return mRing[idx]; } void push_back(T t) { if (mTailIdx == mCapacity) { // Ring buffer is filled to the right, warp around to the beginning // mHeadIdx > 0 must be true, since we checked that as condition (1) above mRing[0] = std::move(t); mTailIdx = 1; } else { mRing[mTailIdx] = std::move(t); mTailIdx += 1; } // Push mHeadIdx backwards if overwrote element in a filled buffer bool bufferFilled = mSize == mCapacity; if (bufferFilled && mTailIdx > mHeadIdx) { mHeadIdx += 1; if (mHeadIdx == mCapacity) { mHeadIdx = 0; } } if (!bufferFilled) { ++mSize; } } [[nodiscard]] size_type capacity() const { return mCapacity; } [[nodiscard]] size_type size() const { return mSize; } [[nodiscard]] T* GetBuffer() const { return mRing; } [[nodiscard]] size_type GetHeadIdx() const { return mHeadIdx; } [[nodiscard]] size_type GetTailIdx() const { return mTailIdx; } void resize(size_type newCapacity) { auto size = this->size(); auto oldRing = mRing; auto newRing = mRing = new T[newCapacity]; std::rotate_copy(oldRing, oldRing + mHeadIdx, oldRing + mCapacity, newRing); delete oldRing; mCapacity = newCapacity; mHeadIdx = 0; mTailIdx = size; } };