From e7af4fdb86a14e026d7c31dccc901210623a3efc Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 1 Oct 2026 22:50:49 +0200 Subject: kstd: ring_buffer: implement copy and move assignment --- libs/kstd/kstd/ring_buffer.hpp | 57 +++++++++++ libs/kstd/kstd/ring_buffer.tests.cpp | 182 +++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) diff --git a/libs/kstd/kstd/ring_buffer.hpp b/libs/kstd/kstd/ring_buffer.hpp index d2f0f958..c332d2e2 100644 --- a/libs/kstd/kstd/ring_buffer.hpp +++ b/libs/kstd/kstd/ring_buffer.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -228,6 +229,62 @@ namespace kstd m_size = count; } + //! Replace the content of this ring buffer with a copy of the content of another one. + //! + //! @param other The ring buffer to copy from. + constexpr auto operator=(ring_buffer const & other) -> ring_buffer & + { + if (this == &other) + { + return *this; + } + + auto const to_copy_assign = std::min(m_size, other.m_size); + std::ranges::copy(std::views::take(other, to_copy_assign), std::ranges::begin(*this)); + + if (to_copy_assign < other.m_size) + { + std::ranges::for_each(std::views::iota(to_copy_assign, other.m_size), + [&](auto const i) { std::construct_at(element_at(i), other[i]); }); + } + else if (to_copy_assign < m_size) + { + std::ranges::for_each(std::views::drop(*this, to_copy_assign), + [](auto & element) { std::destroy_at(&element); }); + } + + m_size = other.m_size; + return *this; + } + + //! Replace the content of this ring buffer by moving from another one. + //! + //! @param other The ring buffer to move from. + constexpr auto operator=(ring_buffer && other) -> ring_buffer & + { + if (this == &other) + { + return *this; + } + + auto const to_move_assign = std::min(m_size, other.m_size); + std::ranges::move(std::views::take(other, to_move_assign), std::ranges::begin(*this)); + + if (to_move_assign < other.m_size) + { + std::ranges::for_each(std::views::iota(to_move_assign, other.m_size), + [&](auto const i) { std::construct_at(element_at(i), std::move(other[i])); }); + } + else if (to_move_assign < m_size) + { + std::ranges::for_each(std::views::drop(*this, to_move_assign), + [](auto & element) { std::destroy_at(&element); }); + } + + m_size = other.m_size; + return *this; + } + //! @} //! @name Element Access diff --git a/libs/kstd/kstd/ring_buffer.tests.cpp b/libs/kstd/kstd/ring_buffer.tests.cpp index 537ed27e..95a235e6 100644 --- a/libs/kstd/kstd/ring_buffer.tests.cpp +++ b/libs/kstd/kstd/ring_buffer.tests.cpp @@ -27,6 +27,47 @@ namespace } }; + struct static_copy_move_tracker : static_dtor_tracker + { + auto static inline copy_ctor_call_count = 0uz; + auto static inline copy_assignment_call_count = 0uz; + auto static inline move_ctor_call_count = 0uz; + auto static inline move_assignment_call_count = 0uz; + + constexpr auto static reset() -> void + { + dtor_call_count = 0; + copy_ctor_call_count = 0; + copy_assignment_call_count = 0; + move_ctor_call_count = 0; + move_assignment_call_count = 0; + } + + constexpr static_copy_move_tracker() = default; + + constexpr static_copy_move_tracker(static_copy_move_tracker const &) + { + ++copy_ctor_call_count; + } + + constexpr static_copy_move_tracker(static_copy_move_tracker &&) + { + ++move_ctor_call_count; + } + + constexpr auto operator=(static_copy_move_tracker const &) -> static_copy_move_tracker & + { + ++copy_assignment_call_count; + return *this; + } + + constexpr auto operator=(static_copy_move_tracker &&) -> static_copy_move_tracker & + { + ++move_assignment_call_count; + return *this; + } + }; + } // namespace SCENARIO("Ring Buffer interface types", "[kstd][ring_buffer]") @@ -348,6 +389,147 @@ SCENARIO("Ring Buffer initialization and construction", "[kstd][ring_buffer]") } } +SCENARIO("Ring Buffer assignment", "[kstd][ring_buffer]") +{ + GIVEN("3 partially populated buffers") + { + auto small = kstd::ring_buffer{2}; + auto same = kstd::ring_buffer{2}; + auto large = kstd::ring_buffer{3}; + + WHEN("copy assigning the small to the large one") + { + static_copy_move_tracker::reset(); + large = small; + + THEN("2 copy assignments and 1 destruction occurs") + { + REQUIRE(static_copy_move_tracker::dtor_call_count == 1); + REQUIRE(static_copy_move_tracker::copy_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_assignment_call_count == 2); + REQUIRE(static_copy_move_tracker::move_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::move_assignment_call_count == 0); + } + } + + WHEN("copy assigning the large to the small one") + { + static_copy_move_tracker::reset(); + small = large; + + THEN("2 copy assignments and 1 copy construction occurs") + { + REQUIRE(static_copy_move_tracker::dtor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_ctor_call_count == 1); + REQUIRE(static_copy_move_tracker::copy_assignment_call_count == 2); + REQUIRE(static_copy_move_tracker::move_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::move_assignment_call_count == 0); + } + } + + WHEN("copy assigning the small to the same size one") + { + static_copy_move_tracker::reset(); + same = small; + + THEN("2 copy assignments occur") + { + REQUIRE(static_copy_move_tracker::dtor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_assignment_call_count == 2); + REQUIRE(static_copy_move_tracker::move_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::move_assignment_call_count == 0); + } + } + + WHEN("copy assigning a buffer to itself") + { + static_copy_move_tracker::reset(); + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wself-assign-overloaded" +#endif + same = same; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + THEN("no operations occur") + { + REQUIRE(static_copy_move_tracker::dtor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_assignment_call_count == 0); + REQUIRE(static_copy_move_tracker::move_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::move_assignment_call_count == 0); + } + } + + WHEN("move assigning the small to the large one") + { + static_copy_move_tracker::reset(); + large = std::move(small); + + THEN("2 move assignments and 1 destruction occurs") + { + REQUIRE(static_copy_move_tracker::dtor_call_count == 1); + REQUIRE(static_copy_move_tracker::copy_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_assignment_call_count == 0); + REQUIRE(static_copy_move_tracker::move_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::move_assignment_call_count == 2); + } + } + + WHEN("move assigning the large to the small one") + { + static_copy_move_tracker::reset(); + small = std::move(large); + + THEN("2 move assignments and 1 move construction occurs") + { + REQUIRE(static_copy_move_tracker::dtor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_assignment_call_count == 0); + REQUIRE(static_copy_move_tracker::move_ctor_call_count == 1); + REQUIRE(static_copy_move_tracker::move_assignment_call_count == 2); + } + } + + WHEN("move assigning the small to the same size one") + { + static_copy_move_tracker::reset(); + same = std::move(small); + + THEN("2 move assignments occur") + { + REQUIRE(static_copy_move_tracker::dtor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_assignment_call_count == 0); + REQUIRE(static_copy_move_tracker::move_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::move_assignment_call_count == 2); + } + } + + WHEN("move assigning a buffer to itself") + { + static_copy_move_tracker::reset(); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wself-move" + same = std::move(same); +#pragma GCC diagnostic pop + + THEN("no operations occur") + { + REQUIRE(static_copy_move_tracker::dtor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::copy_assignment_call_count == 0); + REQUIRE(static_copy_move_tracker::move_ctor_call_count == 0); + REQUIRE(static_copy_move_tracker::move_assignment_call_count == 0); + } + } + } +} + SCENARIO("Ring Buffer destruction", "[kstd][ring_buffer]") { GIVEN("An empty buffer") -- cgit v1.2.3