2
\$\begingroup\$

When I heard about the new Linux I/O interface io_uring I searched about the ring buffer.

I then thought I may replace my safe queue which is base on C++ 11 std::queue with a ring buffer to avoid repeated memory allocation during producing and consuming the packets between threads.

So I wanted to implement it as an exercise and started to look for some C++ implementations and found Boost's circular buffer.

I didn't look at its source code much but looked at the functionality it provides and tried to implement most of them but I still lack some.

So here is what I came up with:

#pragma once
#include <utility>
#include <optional>
#include "span.h"
#include <memory>

#define PROVIDE_CONTAINER_TYPES(T) \
    using value_type = T;\
    using pointer = T * ;\
    using const_pointer = const T *;\
    using reference = T & ;\
    using const_reference = const T &; \
    using size_type = std::size_t; \
    using difference_type = std::ptrdiff_t

struct no_init_t {};
constexpr no_init_t no_init;

class ring_buffer_index
    {
        size_t index;

    public:

        ring_buffer_index() : index{0} {}

        ring_buffer_index(size_t pos) : index{pos} {}

        ring_buffer_index& operator++()
        {
            ++index;
            return *this;
        }

        ring_buffer_index& operator--()
        {
            --index;
            return *this;
        }

        ring_buffer_index operator+(size_t pos) const { return ring_buffer_index{index + pos}; }

        size_t operator-(ring_buffer_index other) const { return index - other.index; }

        bool operator==(ring_buffer_index other) const { return index == other.index; }

        bool operator!=(ring_buffer_index other) const { return index != other.index; }

        void operator+=(size_t times) { index += times; }

        void operator-=(size_t times) { index -= times; }

        void reset() { index = 0; }

        size_t as_index(size_t N) const
        {
            size_t pos = index;
            pos %= N;
            return pos;
        }

    };

    template <class T>
    class uninitialized_array
    {
        std::unique_ptr<unsigned char> raw_buffer;

    public:

        uninitialized_array() noexcept = default;

        uninitialized_array(size_t N) : raw_buffer{ new unsigned char[sizeof(T) * N] } {}

        uninitialized_array(uninitialized_array&& other) noexcept = default;

        uninitialized_array& operator=(uninitialized_array&& other) noexcept = default;

        void copy(const uninitialized_array& other, size_t N)
        {
            raw_buffer.reset();
            if (other.raw_buffer && N)
            {
                raw_buffer.reset(new unsigned char[sizeof(T) * N]);
                std::uninitialized_copy(other.ptr(), other.ptr() + N, ptr());
            }
        }

        void resize(size_t N)
        {
            raw_buffer.reset( new unsigned char[sizeof(T) * N] );
        }

        T * ptr() noexcept { return reinterpret_cast<T*>(raw_buffer.get()); }

        const T * ptr() const noexcept { return reinterpret_cast<const T*>(raw_buffer.get()); }

        T& operator[](size_t pos) noexcept
        {
            return ptr()[pos];
        }

    };

    template <class T, bool reverse = false, bool const_iter = false>
    class ring_buffer_iterator
    {
        T *ptr;
        ring_buffer_index index;
        size_t N;

    public:

        PROVIDE_CONTAINER_TYPES(T);

        using iterator_category = std::random_access_iterator_tag;

        ring_buffer_iterator(T *ptr, ring_buffer_index index, size_t N) : ptr{ ptr }, index{ index }, N{ N } {}

        ring_buffer_iterator& operator++()
        {
            if constexpr (!reverse)
                ++index;
            else
                --index;
            return *this;
        }

        ring_buffer_iterator& operator+=(size_t n) { index += n; return *this; }

        ring_buffer_iterator& operator-=(size_t n) { return operator+=(-n); }

        template <bool citer = const_iter, std::enable_if_t<!citer, bool> = true> 
        reference operator*() const
        {
            return ptr[index.as_index(N)];
        }

        template <bool citer = const_iter, std::enable_if_t<citer, bool> = true>
        const_reference operator*() const
        {
            return ptr[index.as_index(N)];
        }

        template <bool citer = const_iter, std::enable_if_t<!citer, bool> = true>
        reference operator[](difference_type n) { return *(*this + n); }

        const_reference operator[](difference_type n) const { return *(*this + n); }

        bool operator!=(ring_buffer_iterator other) const { return index != other.index; }

        bool operator==(ring_buffer_iterator other) const { return index == other.index; }

        friend ring_buffer_iterator operator+(const ring_buffer_iterator& iter, size_t times) { return ring_buffer_iterator{iter.ptr, iter.index + times, iter.N}; }

        friend ring_buffer_iterator operator+(size_t times, const ring_buffer_iterator& iter) { return ring_buffer_iterator{ iter.ptr, iter.index + times, iter.N }; }

        friend ring_buffer_iterator operator-(const ring_buffer_iterator& lhs, size_t times) { return ring_buffer_iterator{lhs.ptr, lhs.index - times, lhs.N}; }

        friend difference_type operator-(const ring_buffer_iterator& lhs, const ring_buffer_iterator& rhs) { return static_cast<difference_type>(lhs.index - rhs.index); }

    };

    template <class T>
    class ring_buffer
    {
        size_t N;
        mutable uninitialized_array<T> raw_buffer;
        ring_buffer_index read_pos, write_pos;

        T& read_ptr() const
        {
            return raw_buffer[read_pos.as_index(N)];
        }

        T& write_ptr() const
        {
            return raw_buffer[write_pos.as_index(N)];
        }

        bool will_remain_linearized(size_t num)
        {
            ring_buffer_index last_elem = write_pos + num - 1;
            return last_elem.as_index(N) >= read_pos.as_index(N);
        }

    public:

        PROVIDE_CONTAINER_TYPES(T);

        using iterator = ring_buffer_iterator<T>;
        using const_iterator = ring_buffer_iterator<T, false, true>;
        using reverse_iterator = ring_buffer_iterator<T, true>;
        using const_reverse_iterator = const ring_buffer_iterator<T, true, true>;

        /*
        contrtuctors and assignment operators
        */

        ring_buffer() : N{0} {}

        ring_buffer(size_t size) : N{ size }, raw_buffer { size } {}

        ring_buffer(ring_buffer&&) noexcept = default;

        ring_buffer(const ring_buffer& other)
        {
            clear();
            N = other.N;
            read_pos = other.read_pos;
            write_pos = other.write_pos;
            raw_buffer.copy(other.raw_buffer, N);
        }

        ring_buffer(std::initializer_list<value_type> init) : ring_buffer(init.size())
        {
            std::uninitialized_copy(init.begin(), init.end(), raw_buffer.ptr());
        }

        template <class InputIterator>
        ring_buffer(InputIterator first, InputIterator last) : ring_buffer(static_cast<size_type>(std::distance(first, last)))
        {
            std::uninitialized_copy(first, last, raw_buffer.ptr());
            write_pos += capacity();
        }

        ring_buffer& operator=(ring_buffer&& other) noexcept
        {
            clear();
            N = other.N;
            read_pos = other.read_pos;
            write_pos = other.write_pos;
            raw_buffer = std::move(other.raw_buffer);
            return *this;

        }

        ring_buffer& operator=(const ring_buffer& other)
        {
            clear();
            N = other.N;
            read_pos = other.read_pos;
            write_pos = other.write_pos;
            raw_buffer.copy(other.raw_buffer, N);
            return *this;
        }

        ~ring_buffer() { clear(); }

        /*
        addition methods
        */

        /*
        add at the back of the buffer , this is usually used rather than add at the front
        */

        void push_back_without_checks(const value_type& value)
        {
            emplace_back_without_checks(value);
        }

        void push_back_without_checks(value_type&& value)
        {
            emplace_back_without_checks(std::move(value));
        }

        bool try_push_back(const value_type& value)
        {
            return try_emplace_back(value);
        }

        bool try_push_back(value_type&& value)
        {
            return try_emplace_back(std::move(value));
        }

        void push_back(const value_type& value)
        {
            emplace_back(value);
        }

        void push_back(value_type&& value)
        {
            emplace_back(std::move(value));
        }

        template <class ...Args>
        void emplace_back_without_checks(Args&& ... args)
        {
            new(&write_ptr()) T(std::forward<Args>(args)...);
            ++write_pos;
        }

        template <class ...Args>
        bool try_emplace_back(Args&& ... args)
        {
            if (full())
                return false;
            emplace_back_without_checks(std::forward<Args>(args)...);
            return true;
        }

        template <class ...Args>
        void emplace_back(Args&& ... args)
        {
            if (full())
                pop_front();
            emplace_back_without_checks(std::forward<Args>(args)...);
        }

        template <class InputIterator>
        void insert_back(InputIterator first, InputIterator last)
        {
            size_t num = static_cast<size_t>(std::distance(first, last));
            if (will_remain_linearized(num))
            {
                std::uninitialized_copy(first, last, &write_ptr());
                write_pos += num;
            }
            else
                std::copy(first, last, std::back_inserter(*this));
        }

        /*
        add at the front of the buffer
        */

        void push_front_without_checks(const value_type& value)
        {
            emplace_front_without_checks(value);
        }

        void push_front_without_checks(value_type&& value)
        {
            emplace_front_without_checks(value);
        }

        bool try_push_front(const value_type& value)
        {
            return try_emplace_front(value);
        }

        bool try_push_front(value_type&& value)
        {
            return try_emplace_front(value);
        }

        void push_front(const value_type& value)
        {
            emplace_front(value);
        }

        void push_front(value_type&& value)
        {
            emplace_front(value);
        }

        template <class ... Args>
        void emplace_front_without_checks(Args&& ... args)
        {
            --read_pos;
            new (&read_ptr()) T(std::forward<Args>(args)...);
        }

        template <class ... Args>
        bool try_emplace_front(Args&& ... args)
        {
            if (full())
                return false;
            emplace_front_without_checks(std::forward<Args>(args)...);
        }

        template <class ... Args>
        void emplace_front(Args&& ... args)
        {
            if (full())
                pop_back();
            emplace_front_without_checks(std::forward<Args>(args)...);
        }

        template <class InputIterator>
        void insert_front(InputIterator first, InputIterator last)
        {
            std::copy(first, last, std::front_inserter(*this));
        }

        /*
        extraction methods
        */

        /*
        extract from the front of the buffer
        used with back insertion to make a FIFO queue
        */

        void pop_front(value_type& value)
        {
            auto& elem = read_ptr();
            value = std::move(elem);
            elem.~T();
            ++read_pos;
        }

        bool try_pop_front(value_type& value)
        {
            if (empty())
                return false;
            pop_front(value);
            return true;
        }

        void pop_front()
        {
            read_ptr().~T();
            ++read_pos;
        }

        bool try_pop_front()
        {
            if (empty())
                return false;
            pop_front();
            return true;
        }

        // dumps num of first elements into dest where dest points to initialized memory
        template <class OutputIterator>
        void pop_front(OutputIterator dest, size_t num)
        {
            move_from_front(dest, num);
            if constexpr (std::is_pod_v<value_type>)
                read_pos += num;
            else
            {
                while (num--)
                    pop_front();
            }
        }

        // dumps num of first elements into dest where dest points to uninitialized memory
        template <class OutputIterator>
        void pop_front(OutputIterator dest, size_t num, no_init_t)
        {
            move_from_front(dest, num, no_init);
            if constexpr (std::is_pod_v<value_type>)
                read_pos += num;
            else
            {
                while (num--)
                    pop_front();
            }
        }

        template <class OutputIterator>
        bool try_pop_front(OutputIterator dest, size_t num)
        {
            if (num > size())
                return false;
            pop_front(dest, num);
            return true;
        }

        template <class OutputIterator>
        bool try_pop_front(OutputIterator dest, size_t num, no_init_t)
        {
            if (num > size())
                return false;
            pop_front(dest, num, no_init);
            return true;
        }


        /*
        extract from the back of the buffer
        used with back insertion to make a LIFO queue
        */

        void pop_back(value_type& value)
        {
            --write_pos;
            auto& elem = write_ptr();
            value = std::move(elem);
            elem.~T();
        }

        bool try_pop_back(value_type& value)
        {
            if (empty())
                return false;
            pop_back(value);
            return true;
        }

        void pop_back()
        {
            --write_pos;
            write_ptr().~T();
        }

        bool try_pop_back()
        {
            if (empty())
                return false;
            pop_back();
            return true;
        }

        template <class OutputIterator>
        void pop_back(OutputIterator dest, size_t num)
        {
            move_from_back(dest, num);
            if constexpr (std::is_pod_v<value_type>)
                write_pos -= num;
            else
            {
                while (num--)
                    pop_back();
            }
        }

        template <class OutputIterator>
        void pop_back(OutputIterator dest, size_t num, no_init_t)
        {
            move_from_back(dest, num, no_init);
            if constexpr (std::is_pod_v<value_type>)
                write_pos -= num;
            else
            {
                while (num--)
                    pop_back();
            }
        }

        template <class OutputIterator>
        bool try_pop_back(OutputIterator dest, size_t num)
        {
            if (size() < num)
                return false;
            pop_back(dest, num);
            return true;
        }

        template <class OutputIterator>
        bool try_pop_back(OutputIterator dest, size_t num, no_init_t)
        {
            if (size() < num)
                return false;
            pop_back(dest, num, no_init);
            return true;
        }

        /*
        accesors
        */

        reference front() { return read_ptr(); }

        reference back()
        {
            auto pos = write_pos;
            --pos;
            return raw_buffer[pos.as_index(N)];
        }

        const_reference front() const { return read_ptr(); }

        const_reference back() const
        {
            auto pos = write_pos;
            --pos;
            return raw_buffer[pos.as_index(N)];
        }

        reference operator[](size_type pos) noexcept
        {
            auto index = read_pos + pos;
            return raw_buffer[index.as_index(N)];
        }

        const_reference operator[](size_type pos) const noexcept
        {
            auto index = read_pos + pos;
            return raw_buffer[index.as_index(N)];
        }

        /*
        ^ ==> read pointer
        > ==> write pointer
        data starts from read pointer to write pointer

        1 - linearized (from empty to full) : there is one array starting from read pointer to write pointer
        --------------------------------------------------------------------------
        | ^ | 1 | 2 | 3 | 4 | 5 | > |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
        --------------------------------------------------------------------------

        2 - not linearized :
        the first array is from read pointer until the end of the buffer (last index N-1)
        the second array is from the start of the buffer until the write pointer
        --------------------------------------------------------------------------------------
        | 6 | 7 | 8 | > |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  | ^ | 1 | 2 | 3 | 4 | 5 |  
        --------------------------------------------------------------------------------------
        */

        std::span<value_type> array_one() noexcept
        {
            pointer read_pointer = &read_ptr();
            if (is_linearized()) // or empty
                return std::span<value_type>{read_pointer, read_pointer + size()};
            else
                return std::span<value_type>{read_pointer, raw_buffer.ptr() - read_pointer + size()};
        }

        std::span<const value_type> array_one() const noexcept
        {
            pointer read_pointer = &read_ptr();
            if (is_linearized()) // or empty
                return std::span<const value_type>{read_pointer, read_pointer + size()};
            else
                return std::span<const value_type>{read_pointer, raw_buffer.ptr() - read_pointer + size()};
        }

        std::span<value_type> array_two() noexcept
        {
            if (is_linearized())
                return {};
            else
            {
                return std::span<value_type>{raw_buffer.ptr(), &write_ptr()};
            }
        }

        std::span<const value_type> array_two() const noexcept
        {
            if (is_linearized())
                return {};
            else
            {
                return std::span<const value_type>{raw_buffer.ptr(), &write_ptr()};
            }
        }

        void copy_from_front(value_type& value) const
        {
            value = front();
        }

        value_type copy_from_front() const
        {
            value_type value;
            copy_from_front(value);
            return value;
        }

        void move_from_front(value_type& value)
        {
            value = std::move(front());
        }

        value_type move_from_front()
        {
            value_type value;
            move_from_front(value);
            return value;
        }

        template <class OutputIterator>
        void copy_from_front(OutputIterator dest, size_t num) const
        {
            if (is_linearized())
            {
                pointer begin_iter = &read_ptr();
                pointer end_iter = begin_iter + num;
                std::copy(begin_iter, end_iter, dest);
            }
            else
            {
                auto begin_iter = begin();
                auto end_iter = begin_iter + num;
                std::copy(begin_iter, end_iter, dest);
            }
        }

        template <class OutputIterator>
        void copy_from_front(OutputIterator dest, size_t num, no_init_t) const
        {
            if (is_linearized())
            {
                pointer begin_iter = &read_ptr();
                pointer end_iter = begin_iter + num;
                std::uninitialized_copy(begin_iter, end_iter, dest);
            }
            else
            {
                auto begin_iter = begin();
                auto end_iter = begin_iter + num;
                std::uninitialized_copy(begin_iter, end_iter, dest);
            }
        }

        template <class OutputIterator>
        void move_from_front(OutputIterator dest, size_t num)
        {
            if (is_linearized())
            {
                pointer begin_iter = &read_ptr();
                pointer end_iter = begin_iter + num;
                std::copy(std::make_move_iterator(begin_iter), std::make_move_iterator(end_iter), dest);
            }
            else
            {
                auto begin_iter = begin();
                auto end_iter = begin_iter + num;
                std::copy(std::make_move_iterator(begin_iter), std::make_move_iterator(end_iter), dest);
            }
        }

        template <class OutputIterator>
        void move_from_front(OutputIterator dest, size_t num, no_init_t)
        {
            if (is_linearized())
            {
                pointer begin_iter = &read_ptr();
                pointer end_iter = begin_iter + num;
                std::uninitialized_move(begin_iter, end_iter, dest);
            }
            else
            {
                auto begin_iter = begin();
                auto end_iter = begin_iter + num;
                std::uninitialized_move(begin_iter, end_iter, dest);
            }
        }

        void copy_from_back(value_type& value) const
        {
            value = back();
        }

        value_type copy_from_back() const
        {
            value_type value;
            copy_from_back(value);
            return value;
        }

        void move_from_back(value_type& value)
        {
            value = std::move(back());
        }

        value_type move_from_back()
        {
            value_type value;
            move_from_back(value);
            return value;
        }

        template <class OutputIterator>
        void copy_from_back(OutputIterator dest, size_t num) const
        {
            if (is_linearized())
            {
                pointer end_iter = &write_ptr();
                pointer first = end_iter - num;
                std::copy(first, end_iter, dest);
            }
            else
            {
                auto end_iter = end();
                auto first = end_iter - num;
                std::copy(first, end_iter, dest);
            }
        }

        template <class OutputIterator>
        void copy_from_back(OutputIterator dest, size_t num, no_init_t) const
        {
            if (is_linearized())
            {
                pointer end_iter = &write_ptr();
                pointer first = end_iter - num;
                std::uninitialized_copy(first, end_iter, dest);
            }
            else
            {
                auto end_iter = end();
                auto first = end_iter - num;
                std::uninitialized_copy(first, end_iter, dest);
            }
        }

        template <class OutputIterator>
        void move_from_back(OutputIterator dest, size_t num)
        {
            if (is_linearized())
            {
                pointer end_iter = &write_ptr();
                pointer first = end_iter - num;
                std::copy(std::make_move_iterator(first), std::make_move_iterator(end_iter), dest);
            }
            else
            {
                auto end_iter = end();
                auto first = end_iter - num;
                std::copy(std::make_move_iterator(first), std::make_move_iterator(end_iter), dest);
            }
        }

        template <class OutputIterator>
        void move_from_back(OutputIterator dest, size_t num, no_init_t)
        {
            if (is_linearized())
            {
                pointer end_iter = &write_ptr();
                pointer first = end_iter - num;
                std::uninitialized_move(first, end_iter, dest);
            }
            else
            {
                auto end_iter = end();
                auto first = end_iter - num;
                std::uninitialized_move(first, end_iter, dest);
            }
        }

        /*
        range methods
        */

        iterator begin() noexcept { return iterator{raw_buffer.ptr(), read_pos, N}; }

        iterator end() noexcept { return iterator{raw_buffer.ptr(), write_pos, N}; }

        const_iterator begin() const noexcept { return const_iterator{raw_buffer.ptr(), read_pos, N}; }

        const_iterator end() const noexcept { return const_iterator{raw_buffer.ptr(), write_pos, N}; }

        const_iterator cbegin() const noexcept { return begin(); }

        const_iterator cend() const noexcept { return end(); }

        reverse_iterator rbegin() noexcept { return reverse_iterator{ raw_buffer.ptr(), write_pos - 1, N }; }

        reverse_iterator rend() noexcept { return reverse_iterator{ raw_buffer.ptr(), read_pos - 1, N }; }

        const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{ raw_buffer.ptr(), write_pos - 1, N }; }

        const_reverse_iterator rend() const noexcept { return const_reverse_iterator{ raw_buffer.ptr(), read_pos - 1, N }; }

        const_reverse_iterator crbegin() const noexcept { return rbegin(); }

        const_reverse_iterator crend() const noexcept { return rend(); }


        /*
        eraser, linearization, resize and some info
        */

        void clear()
        {
            if constexpr (std::is_pod_v<value_type>)
            {
                read_pos.reset();
                write_pos.reset();
            }
            else
            {
                while (!empty())
                    pop_front();
            }
        }

        pointer linearize()
        {
            ring_buffer rbf(N);
            auto first_array = array_one();
            rbf.insert_back(std::make_move_iterator(first_array.begin()), std::make_move_iterator(first_array.end()));
            auto second_array = array_two();
            rbf.insert_back(std::make_move_iterator(second_array.begin()), std::make_move_iterator(second_array.end()));
            this->operator=(std::move(rbf));
            return raw_buffer.ptr();
        }

        template <class OutputIterator>
        void linearize(OutputIterator dest) const
        {
            auto first_array = array_one();
            std::copy(first_array.begin(), first_array.end(), dest);
            auto second_array = array_two();
            std::copy(second_array.begin(), second_array.end(), dest + first_array.size());
        }

        void set_capacity(size_type Num)
        {
            clear();
            N = Num;
            raw_buffer.resize(Num);
        }

        size_t capacity() const noexcept { return N; }

        size_t size() const noexcept { return write_pos - read_pos; }

        size_t available_size() const noexcept { return capacity() - size(); }

        size_t reserve() const noexcept { return available_size(); }

        bool full() const noexcept { return size() == capacity(); }

        bool empty() const noexcept { return !size(); }

        bool is_linearized() const noexcept 
        {
            ring_buffer_index last_pos = write_pos - 1;
            return last_pos.as_index(N) >= read_pos.as_index(N);
        }

        /*
        aliases to use the ring buffer as FIFO
        */

        template <class ...Args>
        void emplace(Args&& ... args)
        {
            emplace_back(std::forward<Args>(args)...);
        }

        void push(const value_type& value)
        {
            push_back(value);
        }

        void push(value_type&& value)
        {
            push_back(std::move(value));
        }

        template <class InputIterator>
        void insert(InputIterator first, InputIterator last)
        {
            insert_back(first, last);
        }

        void pop(value_type& value)
        {
            pop_front(value);
        }

        bool try_pop(value_type& value)
        {
            return try_pop_front(value);
        }

        void pop()
        {
            pop_front();
        }

        bool try_pop()
        {
            return try_pop_front();
        }

        template <class OutputIt>
        void pop(OutputIt dest, size_t num)
        {
            pop_front(dest, num);
        }

        template <class OutputIt>
        void pop(OutputIt dest, size_t num, no_init_t)
        {
            pop_front(dest, num, no_init);
        }

        template <class OutputIt>
        bool try_pop(OutputIt dest, size_t num)
        {
            return try_pop_front(dest, num);
        }

        template <class OutputIt>
        bool try_pop(OutputIt dest, size_t num, no_init_t)
        {
            return try_pop_front(dest, num, no_init);
        }

    };

The span.h header can be found here.

I found it somewhere on the internet since some months ago, but I can't recall where did I get it from and as said in the title it's an implementation for the new C++ 20 span which represents a view for a range of contiguous memory, like string_view, but it enables to non const access the values if it isn't const.

I want to know:

  • Does the implementation misuse the concept of ring buffer?
  • Do the iterators satisfy the C++ concept of iterators?
  • Is there any drawbacks of using the indexes as counters and obtaining the real indexes with modulus especially for bigger values?
\$\endgroup\$
8
  • 2
    \$\begingroup\$ The class SomeClass makes this question off-topic because it seems to be hypothetical. Is the class necessary to the question, and if it is could you better define it? \$\endgroup\$
    – pacmaninbw
    Commented Nov 15, 2019 at 17:46
  • \$\begingroup\$ I don't understand what are you saying but I wrote it as an example to illustrate why I favored overwriting by destructing and constructing rather than by assigning . if you see it's better to reassign then I'll be pleased to hear your opinion \$\endgroup\$
    – dev65
    Commented Nov 15, 2019 at 17:52
  • 1
    \$\begingroup\$ The name of the class and possibly the usage makes it hypothetical. We review code and suggest how it can be improved. Reassigning versus destruction/construction might be considered opinion based and off-topic. \$\endgroup\$
    – pacmaninbw
    Commented Nov 15, 2019 at 17:56
  • \$\begingroup\$ I don't know why it's off-topic . the boost's website claims that reassigning is more efficient \$\endgroup\$
    – dev65
    Commented Nov 15, 2019 at 18:01
  • \$\begingroup\$ codereview.stackexchange.com/help/how-to-ask and codereview.stackexchange.com/help/dont-ask. \$\endgroup\$
    – pacmaninbw
    Commented Nov 15, 2019 at 18:07

2 Answers 2

3
\$\begingroup\$

After some searching I found that my ring buffer is broken if its capacity isn't power of 2, because once the counter reaches its maximum (on 64-bit : 2^64 - 1) it will begin from zero the next increment so if the modulus of the counter by n isn't n-1 when it reaches the maximum unsigned integer of the architecture then the counter will misbehave because at the next step the remainder (index) will be 0 while the previous remainder (index) isn't n - 1

this illustrates the problem :

constexpr uint64_t rem1 = std::numeric_limits<uint64_t>::max() % 13;
cout << "rem1 = " << rem1 << endl; // rem1 = 2
constexpr uint64_t rem2 = (std::numeric_limits<uint64_t>::max() + 1) % 13; // std::numeric_limits<uint64_t>::max() + 1 = 0
cout << "rem2 = " << rem2 << endl; // rem2 = 0 not 3 !

but for number n equals power of two the remainder will be n -1 at maximum value so no problem here

\$\endgroup\$
0
\$\begingroup\$

As long as the index values are not insanely high, you could use recursion and still have odd size arrays.

size_t as_index(size_t N, size_t index)
{
    if (pos < N)
        return pos;
    return as_index(N, pos - N);
}

Loved the code by the way. The only real change I did is remove ring_buffer_index (since all you need is as_index) and uninitialized_array (as I could swap it with a std::span and wanted to use fixed arrays).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.