#pragma once #include #include #include #include template class RingBuffer { public: class Sentinel {}; class Iterator { public: friend class RingBuffer; using difference_type = ptrdiff_t; using value_type = T; using pointer = T*; using reference = T&; using iterator_category = std::random_access_iterator_tag; private: RingBuffer* mContainer; size_t mCurr; bool mNeedsWrapAround; bool mHasWrapped = false; public: // NOTE: default constructed Iterator is in an undefined state T& operator*() const { return mContainer->mRing[mCurr]; } Iterator& operator++() { assert(*this != Sentinel{}); ++mCurr; if (mNeedsWrapAround && mCurr == mContainer->mCapacity) { mHasWrapped = true; mCurr = 0; } return *this; } bool operator==(const Iterator& that) const { assert(this->mContainer == that.mContainer); return this->mCurr == that.mCurr; } bool operator==(const Sentinel&) const { return mCurr == mContainer->mTailIdx && (!mNeedsWrapAround || mHasWrapped); } }; using value_type = T; using reference = T&; using const_reference = const T&; using iterator = Iterator; // using const_iterator = void; // TODO using difference_type = ptrdiff_t; using size_type = size_t; private: T* mRing = nullptr; size_t mHeadIdx = 0; size_t mTailIdx = 0; size_t mCapacity = 0; size_t 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; } Iterator begin() { Iterator it; it.mContainer = this; it.mCurr = mHeadIdx; it.mNeedsWrapAround = mHeadIdx >= mTailIdx; return it; } Sentinel end() { return Sentinel{}; } const T& operator[](size_t i) const { size_t idx = mHeadIdx + i; if (idx >= mCapacity) { idx -= mCapacity; } return mRing[idx]; } T& operator[](size_t i) { return const_cast(const_cast(*this)[i]); } 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_t capacity() const { return mCapacity; } [[nodiscard]] size_t size() const { return mSize; } [[nodiscard]] T* GetBuffer() const { return mRing; } [[nodiscard]] size_t GetHeadIdx() const { return mHeadIdx; } [[nodiscard]] size_t GetTailIdx() const { return mTailIdx; } void resize(size_t 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; } };