aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/kstd/kstd/ring_buffer.hpp35
-rw-r--r--libs/kstd/kstd/ring_buffer.tests.cpp250
2 files changed, 285 insertions, 0 deletions
diff --git a/libs/kstd/kstd/ring_buffer.hpp b/libs/kstd/kstd/ring_buffer.hpp
index 7b9012aa..152bf697 100644
--- a/libs/kstd/kstd/ring_buffer.hpp
+++ b/libs/kstd/kstd/ring_buffer.hpp
@@ -774,6 +774,41 @@ namespace kstd
//! @}
+ //! @name Swap
+ //! @{
+
+ //! Swap the contents of this ring buffer with another.
+ //!
+ //! @param other The other ring buffer to swap with.
+ constexpr auto friend swap(ring_buffer & lhs,
+ ring_buffer & rhs) noexcept(std::is_nothrow_swappable_v<value_type> &&
+ std::is_nothrow_move_constructible_v<value_type>) -> void
+ requires(Capacity > 0)
+ {
+ auto const to_swap = std::min(lhs.m_size, rhs.m_size);
+ std::ranges::for_each(std::views::iota(0uz, to_swap),
+ [&](auto i) { std::ranges::swap(*lhs.element_at(i), *rhs.element_at(i)); });
+
+ if (rhs.m_size > lhs.m_size)
+ {
+ std::ranges::for_each(std::views::iota(to_swap, rhs.m_size), [&](auto i) {
+ std::construct_at(lhs.element_at(i), std::move(*rhs.element_at(i)));
+ std::destroy_at(rhs.element_at(i));
+ });
+ }
+ else if (lhs.m_size > to_swap)
+ {
+ std::ranges::for_each(std::views::iota(to_swap, lhs.m_size), [&](auto i) {
+ std::construct_at(rhs.element_at(i), std::move(*lhs.element_at(i)));
+ std::destroy_at(lhs.element_at(i));
+ });
+ }
+
+ std::ranges::swap(lhs.m_size, rhs.m_size);
+ }
+
+ //! @}
+
private:
//! Get a pointer to the element at the given read-index relative position.
//!
diff --git a/libs/kstd/kstd/ring_buffer.tests.cpp b/libs/kstd/kstd/ring_buffer.tests.cpp
index b08ee222..d870c2f8 100644
--- a/libs/kstd/kstd/ring_buffer.tests.cpp
+++ b/libs/kstd/kstd/ring_buffer.tests.cpp
@@ -253,6 +253,11 @@ SCENARIO("Ring Buffer interface types", "[kstd][ring_buffer]")
{
STATIC_REQUIRE(std::is_same_v<buffer::reference, decltype(std::declval<buffer &>().emplace_back(0.0f))>);
}
+
+ THEN("the return type of swap() is void")
+ {
+ STATIC_REQUIRE(std::is_same_v<void, decltype(swap(std::declval<buffer &>(), std::declval<buffer &>()))>);
+ }
}
}
@@ -2094,4 +2099,249 @@ SCENARIO("Ring Buffer comparison", "[kstd][ring_buffer]")
REQUIRE(lhs == rhs);
}
}
+}
+
+SCENARIO("Ring Buffer swap", "[kstd][ring_buffer]")
+{
+ GIVEN("Two ring buffers of equal size, both freshly constructed")
+ {
+ auto a = kstd::ring_buffer<int, 5>{3, 1};
+ auto b = kstd::ring_buffer<int, 5>{3, 2};
+
+ WHEN("swapping them")
+ {
+ swap(a, b);
+
+ THEN("their sizes are unchanged")
+ {
+ REQUIRE(a.size() == 3);
+ REQUIRE(b.size() == 3);
+ }
+
+ THEN("their contents are exchanged")
+ {
+ REQUIRE_THAT(a, Catch::Matchers::RangeEquals({2, 2, 2}));
+ REQUIRE_THAT(b, Catch::Matchers::RangeEquals({1, 1, 1}));
+ }
+ }
+ }
+
+ GIVEN("Two ring buffers with the same logical content but different physical layouts")
+ {
+ auto fresh = kstd::ring_buffer<int, 5>{};
+ fresh.push_back(10);
+ fresh.push_back(20);
+ fresh.push_back(30);
+
+ auto wrapped = kstd::ring_buffer<int, 5>{};
+ for (int v : {1, 2, 3, 4, 5})
+ {
+ wrapped.push_back(v);
+ }
+ wrapped.pop_front();
+ wrapped.pop_front();
+
+ WHEN("swapping them")
+ {
+ swap(fresh, wrapped);
+
+ THEN("each buffer ends up holding the other's logical content")
+ {
+ REQUIRE_THAT(fresh, Catch::Matchers::RangeEquals({3, 4, 5}));
+ REQUIRE_THAT(wrapped, Catch::Matchers::RangeEquals({10, 20, 30}));
+ }
+ }
+ }
+
+ GIVEN("A smaller and a larger ring buffer")
+ {
+ auto small = kstd::ring_buffer<int, 5>{2, 7};
+ auto large = kstd::ring_buffer<int, 5>{5, 100};
+
+ WHEN("swapping with the smaller buffer passed first")
+ {
+ swap(small, large);
+
+ THEN("the sizes are exchanged")
+ {
+ REQUIRE(small.size() == 5);
+ REQUIRE(large.size() == 2);
+ }
+
+ THEN("the contents are exchanged")
+ {
+ REQUIRE_THAT(small, Catch::Matchers::RangeEquals({100, 100, 100, 100, 100}));
+ REQUIRE_THAT(large, Catch::Matchers::RangeEquals({7, 7}));
+ }
+ }
+
+ WHEN("swapping with the larger buffer passed first")
+ {
+ swap(large, small);
+
+ THEN("the sizes are exchanged")
+ {
+ REQUIRE(large.size() == 2);
+ REQUIRE(small.size() == 5);
+ }
+
+ THEN("the contents are exchanged")
+ {
+ REQUIRE_THAT(large, Catch::Matchers::RangeEquals({7, 7}));
+ REQUIRE_THAT(small, Catch::Matchers::RangeEquals({100, 100, 100, 100, 100}));
+ }
+ }
+ }
+
+ GIVEN("Two ring buffers that differ in both size and physical layout")
+ {
+ auto a = kstd::ring_buffer<int, 5>{};
+ for (int v : {1, 2, 3, 4, 5, 6})
+ {
+ a.push_back(v);
+ }
+ a.pop_front();
+
+ auto b = kstd::ring_buffer<int, 5>{};
+ for (int v : {10, 20, 30, 40, 50})
+ {
+ b.push_back(v);
+ }
+ b.pop_front();
+ b.pop_front();
+ b.pop_front();
+
+ WHEN("swapping them")
+ {
+ swap(a, b);
+
+ THEN("the sizes are exchanged")
+ {
+ REQUIRE(a.size() == 2);
+ REQUIRE(b.size() == 4);
+ }
+
+ THEN("the contents are exchanged")
+ {
+ REQUIRE_THAT(a, Catch::Matchers::RangeEquals({40, 50}));
+ REQUIRE_THAT(b, Catch::Matchers::RangeEquals({3, 4, 5, 6}));
+ }
+ }
+ }
+
+ GIVEN("One empty and one non-empty ring buffer")
+ {
+ auto empty = kstd::ring_buffer<int, 5>{};
+ auto full = kstd::ring_buffer<int, 5>{3, 10};
+
+ WHEN("swapping them")
+ {
+ swap(empty, full);
+
+ THEN("the previously empty buffer now holds the content")
+ {
+ REQUIRE_THAT(empty, Catch::Matchers::RangeEquals({10, 10, 10}));
+ }
+
+ THEN("the previously full buffer is now empty")
+ {
+ REQUIRE(full.empty());
+ }
+ }
+ }
+
+ GIVEN("Two empty ring buffers")
+ {
+ auto a = kstd::ring_buffer<int, 5>{};
+ auto b = kstd::ring_buffer<int, 5>{};
+
+ WHEN("swapping them")
+ {
+ swap(a, b);
+
+ THEN("both remain empty")
+ {
+ REQUIRE(a.empty());
+ REQUIRE(b.empty());
+ }
+ }
+ }
+
+ GIVEN("A ring buffer swapped with itself")
+ {
+ auto buffer = kstd::ring_buffer<int, 5>{3, 1};
+ buffer.at(1) = 2;
+ buffer.at(2) = 3;
+
+ WHEN("swapping it with itself")
+ {
+ swap(buffer, buffer);
+
+ THEN("its content is unchanged")
+ {
+ REQUIRE_THAT(buffer, Catch::Matchers::RangeEquals({1, 2, 3}));
+ }
+ }
+ }
+
+ GIVEN("Two partially filled ring buffers of static operation trackers, equal size")
+ {
+ auto a = kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 5>{3};
+ auto b = kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 5>{3};
+
+ WHEN("swapping them")
+ {
+ kstd::tests::static_copy_move_tracker::reset();
+ swap(a, b);
+
+ THEN("each element is exchanged via one move construction and two move assignments, nothing is leaked")
+ {
+ REQUIRE(kstd::tests::static_copy_move_tracker::move_ctor_call_count == 3);
+ REQUIRE(kstd::tests::static_copy_move_tracker::move_assignment_call_count == 6);
+ REQUIRE(kstd::tests::static_copy_move_tracker::dtor_call_count == 3);
+ REQUIRE(kstd::tests::static_copy_move_tracker::copy_ctor_call_count == 0);
+ REQUIRE(kstd::tests::static_copy_move_tracker::copy_assignment_call_count == 0);
+ }
+ }
+ }
+
+ GIVEN("Two ring buffers of static operation trackers with different sizes")
+ {
+ auto large = kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 5>{5};
+ auto small = kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 5>{2};
+
+ WHEN("swapping them")
+ {
+ kstd::tests::static_copy_move_tracker::reset();
+ swap(large, small);
+
+ THEN("the shared prefix is exchanged by swapping, the remainder by move-construction, nothing is leaked")
+ {
+ REQUIRE(kstd::tests::static_copy_move_tracker::move_ctor_call_count == 5);
+ REQUIRE(kstd::tests::static_copy_move_tracker::move_assignment_call_count == 4);
+ REQUIRE(kstd::tests::static_copy_move_tracker::dtor_call_count == 5);
+ REQUIRE(kstd::tests::static_copy_move_tracker::copy_ctor_call_count == 0);
+ REQUIRE(kstd::tests::static_copy_move_tracker::copy_assignment_call_count == 0);
+ }
+ }
+ }
+
+ GIVEN("A value type whose move operations are noexcept")
+ {
+ THEN("swap() is noexcept")
+ {
+ STATIC_REQUIRE(
+ noexcept(swap(std::declval<kstd::ring_buffer<int, 5> &>(), std::declval<kstd::ring_buffer<int, 5> &>())));
+ }
+ }
+
+ GIVEN("A value type whose move operations are not noexcept")
+ {
+ THEN("swap() is not noexcept")
+ {
+ STATIC_REQUIRE_FALSE(
+ noexcept(swap(std::declval<kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 5> &>(),
+ std::declval<kstd::ring_buffer<kstd::tests::static_copy_move_tracker, 5> &>())));
+ }
+ }
} \ No newline at end of file