4
\$\begingroup\$

As the title states, this is my 4th revision of this class. Here is the previous post if needed.

I'm hoping someone experienced with things such as noexcept, [[nodiscard]], etc can go over the uses in the class below any make sure I'm on the right track?

Further more, I've attempted to implement the copy and swap and would like to know if my implementation looks accurate?

I've changed data private member variable to std::array from the previous "c-style" array, was that a reasonable thing to do?

I've noticed that std::aligned_storage is in <type_traits> which also appears to supply an std::move and std::forward but this mentions forward is in <utility>, that site also shows std::move in <utility> or <algorithm> how do I know if I'm including the correct files?

I'm also looking for any feedback on overlooked bugs/problems with my implementation, as well as any other feature suggestions. (I'm attempting to use this class as a standard for my future work so I'm trying to pack as much knowledge into it as possible.)

#pragma once // not looking for feedback on include guard.

#include <new> // std::launder
#include <array> // std::array
#include <cstddef> // std::size_t
#include <cassert> // assert
#include <type_traits> // std::aligned_storage_t, std::forward, std::move

#include "../LangMacros.h" // NO_DISCARD

namespace datastructures {

    template<class T, std::size_t N>
    class RingBuffer {

        std::array<std::aligned_storage_t<sizeof(T), alignof(T)>, N> data;
        std::size_t head;
        std::size_t tail;
        bool isFull;

        NO_DISCARD T* ptr_to(std::size_t pos) noexcept {
            return std::launder(reinterpret_cast<T*>(&data[pos]));
        }

        NO_DISCARD const T* ptr_to(std::size_t pos) const noexcept {
            return std::launder(reinterpret_cast<const T*>(&data[pos]));
        }

    public:
        // default
        constexpr RingBuffer() noexcept :
            data{},
            head{0},
            tail{0},
            isFull{false} {
        };

        // copy
        RingBuffer(const RingBuffer& other) :
            data{},
            head{other.head},
            tail{other.tail},
            isFull{other.isFull}
        {
            // can this function be noexcept?
            if (!is_empty())
                // ignore bug with this for loop iteration
                for (std::size_t i = tail; i < head; i = (i + 1) % N)
                    new (&data[i]) T(*other.ptr_to(i)); // `other.data[i]` give cosnt _Ty which causes error, why?
        }

        // move
        /* replaced with "swap-move" below instead
        RingBuffer(RingBuffer&& other) :
            data{},
            head{other.head},
            tail{other.tail},
            isFull{other.isFull}
        {
            // note: not exception safe
            if (!is_empty())
                for (std::size_t i = tail; i < head; i = (i + 1) % N)
                    new (&data[i]) T(std::move(other.data[i]));
        }
        */

        // swap-move
        RingBuffer(RingBuffer&& other) :
            RingBuffer() // initialize via default constructor, C++11 only
        {
            swap(*this, other);
        }

        ~RingBuffer() noexcept {
            clear();
        }

        // using https://stackoverflow.com/a/3279550/4831051 as a reference, can this be constexpr / noexcept?
        friend void swap(RingBuffer& first, RingBuffer& second) /*nothrow*/ {
            using std::swap;

            swap(first.data, second.data); //TODO is this safe?!
            swap(first.head, second.head);
            swap(first.tail, second.tail);
            swap(first.isFull, second.isFull);
        }

        RingBuffer& operator=(RingBuffer other) {
            swap(*this, other);

            return *this;
        }

        void clear() noexcept {
            while (!is_empty()) pop();
        }

        template<typename ...Args>
        void emplace_push(Args&&... args) noexcept {
            assert(!isFull && "error: buffer is full!");

            new (&data[head]) T(std::forward<Args>(args)...);

            head = (head + 1) % N;
            isFull = head == tail;
        }

        void push(T item) noexcept {
            emplace_push(std::move(item));
        }

        void pop() noexcept {
            assert(!is_empty() && "error: buffer is empty!");

            ptr_to(tail)->~T();

            tail = (tail + 1) % N;
            isFull = false;
        }

        NO_DISCARD constexpr T& peek() noexcept {
            assert(!is_empty() && "error: buffer is empty!");

            return *ptr_to(tail);
        }

        NO_DISCARD constexpr const T& peek() const noexcept {
            assert(!is_empty() && "error: buffer is empty!");

            return *ptr_to(tail);
        }

        NO_DISCARD constexpr bool is_empty() const noexcept {
            return !isFull && tail == head;
        }

        NO_DISCARD constexpr std::size_t get_capacity() const noexcept {
            return N;
        }

        NO_DISCARD constexpr std::size_t get_size() const noexcept {
            if (isFull)
                return N;

            if (head >= tail)
                return head - tail;

            return N + head - tail;
        }
    };
}
\$\endgroup\$
  • 2
    \$\begingroup\$ There are two distinct function templates called move. The one you are looking for is declared in <utility>. \$\endgroup\$ – L. F. May 27 at 9:47
  • \$\begingroup\$ @L.F.Thanks for that clarification. \$\endgroup\$ – Hex May 28 at 0:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.