Skip to content

Commit 002c14f

Browse files
committed
Enhance ring buffer implementation with additional features and tests
- Updated CMakeLists.txt to define project and include Google Test conditionally. - Expanded .gitignore to include build artifacts and temporary testing files. - Improved RingBuffer class with move semantics, swap functionality, and additional iterator methods. - Added comprehensive unit tests for copy behavior, iterator equality, and buffer state after operations. - Updated README with FetchContent integration instructions and testing policy.
1 parent 7ce9298 commit 002c14f

5 files changed

Lines changed: 403 additions & 21 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
.idea
22
cmake-build-debug
33
cmake-build-release
4+
build
5+
Testing/Temporary
6+

CMakeLists.txt

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cmake_minimum_required(VERSION 3.21)
2-
project(RingBufferTest)
2+
project(RingBuffer LANGUAGES CXX)
33

44
set(Simulate_Android_ToolChain OFF)
55
if (Simulate_Android_ToolChain)
@@ -9,19 +9,23 @@ else()
99
set(CMAKE_CXX_STANDARD 17)
1010
endif()
1111

12-
# Add Google Test
13-
# Include FetchContent to download and manage external dependencies
14-
include(FetchContent)
15-
FetchContent_Declare(
16-
googletest
17-
URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip
18-
)
19-
FetchContent_MakeAvailable(googletest)
12+
add_library(RingBuffer INTERFACE)
13+
add_library(RingBuffer::RingBuffer ALIAS RingBuffer)
14+
target_include_directories(RingBuffer INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
2015

21-
enable_testing()
22-
add_executable(RingBufferTest test_main.cpp)
23-
target_link_libraries(RingBufferTest gtest gtest_main)
16+
option(RINGBUFFER_BUILD_TESTS "Build RingBuffer tests" ON)
17+
if (RINGBUFFER_BUILD_TESTS)
18+
include(FetchContent)
19+
FetchContent_Declare(
20+
googletest
21+
URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip
22+
)
23+
FetchContent_MakeAvailable(googletest)
2424

25+
enable_testing()
26+
add_executable(RingBufferTest test_main.cpp)
27+
target_link_libraries(RingBufferTest gtest gtest_main RingBuffer::RingBuffer)
2528

26-
include(GoogleTest)
27-
gtest_discover_tests(RingBufferTest)
29+
include(GoogleTest)
30+
gtest_discover_tests(RingBufferTest)
31+
endif()

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ This project demonstrates many modern C++ best practices:
4646
### Build Instructions
4747

4848
```bash
49-
git clone https://github.com/yourusername/RingBuffer.git
50-
cd RingBuffer
49+
git clone https://github.com/bugparty/RingBufferCpp.git
50+
cd RingBufferCpp
5151
mkdir build && cd build
5252
cmake ..
5353
cmake --build .
@@ -60,6 +60,39 @@ This will:
6060

6161
---
6262

63+
## FetchContent Integration
64+
65+
```cmake
66+
include(FetchContent)
67+
FetchContent_Declare(
68+
RingBuffer
69+
GIT_REPOSITORY https://github.com/bugparty/RingBufferCpp.git
70+
GIT_TAG main
71+
)
72+
FetchContent_MakeAvailable(RingBuffer)
73+
74+
target_link_libraries(your_target PRIVATE RingBuffer::RingBuffer)
75+
```
76+
77+
To skip building tests in your parent project, set:
78+
79+
```cmake
80+
set(RINGBUFFER_BUILD_TESTS OFF)
81+
```
82+
83+
---
84+
85+
## Testing Policy
86+
87+
When changing the library, run the tests before pushing changes:
88+
89+
```bash
90+
cmake --build build
91+
ctest --output-on-failure --test-dir build
92+
```
93+
94+
---
95+
6396
Usage
6497

6598
```cpp

RingBuffer.hpp

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <type_traits>
88
#include <algorithm>
99
#include <cstring>
10+
#include <utility>
1011
#include <vector>
1112
#pragma once
1213
namespace buffers {
@@ -62,12 +63,12 @@ namespace buffers {
6263
[[nodiscard]] const_pointer operator->() const noexcept {
6364
return &((*source_)[index_]);
6465
}
65-
[[nodiscard]] self_type& operator++() noexcept {
66+
self_type& operator++() noexcept {
6667
index_ = ++index_ % N;
6768
++count_;
6869
return *this;
6970
}
70-
[[nodiscard]] self_type operator++(int) noexcept {
71+
self_type operator++(int) noexcept {
7172
auto result = *this;
7273
++(*this);
7374
return result;
@@ -78,6 +79,9 @@ namespace buffers {
7879
[[nodiscard]] size_type count() const noexcept {
7980
return count_;
8081
}
82+
[[nodiscard]] buffer_t source() const noexcept {
83+
return source_;
84+
}
8185
~ring_buffer_iterator() = default;
8286
private:
8387
buffer_t source_{};
@@ -88,13 +92,13 @@ namespace buffers {
8892
template<typename T, size_t N, bool C, bool Overwrite>
8993
bool operator==(ring_buffer_iterator<T,N,C,Overwrite> const& l,
9094
ring_buffer_iterator<T,N,C,Overwrite> const& r) noexcept {
91-
return l.count() == r.count();
95+
return l.source() == r.source() && l.count() == r.count() && l.index() == r.index();
9296
}
9397

9498
template<typename T, size_t N, bool C, bool Overwrite>
9599
bool operator!=(ring_buffer_iterator<T,N,C,Overwrite> const& l,
96100
ring_buffer_iterator<T,N,C,Overwrite> const& r) noexcept {
97-
return l.count() != r.count();
101+
return l.source() != r.source() || l.count() != r.count() || l.index() != r.index();
98102
}
99103

100104
}
@@ -134,11 +138,14 @@ using std::bool_constant;
134138
using iterator = detail::ring_buffer_iterator<T, N, false, Overwrite>;
135139
using const_iterator = detail::ring_buffer_iterator<T, N, true, Overwrite>;
136140

141+
// Create an empty ring buffer.
137142
ring_buffer() noexcept = default;
143+
// Copy contents and state from another buffer.
138144
ring_buffer(ring_buffer const& rhs) noexcept(is_nothrow_copy_constructible_v<value_type>)
139145
{
140146
copy_impl(rhs, bool_constant<is_trivially_copyable_v<T>>{});
141147
}
148+
// Assign from another buffer.
142149
ring_buffer& operator=(ring_buffer const& rhs) noexcept(is_nothrow_copy_constructible_v<value_type>) {
143150
if(this == &rhs)
144151
return *this;
@@ -148,10 +155,31 @@ using std::bool_constant;
148155

149156
return *this;
150157
}
158+
// Move contents and state from another buffer.
159+
ring_buffer(ring_buffer&& rhs) noexcept(std::is_nothrow_move_constructible<value_type>::value)
160+
{
161+
move_impl(rhs, bool_constant<is_trivially_copyable_v<T>>{});
162+
}
163+
// Move-assign from another buffer.
164+
ring_buffer& operator=(ring_buffer&& rhs) noexcept(std::is_nothrow_move_constructible<value_type>::value) {
165+
if(this == &rhs)
166+
return *this;
167+
168+
destroy_all(bool_constant<is_trivially_copyable_v<T>>{});
169+
move_impl(rhs, bool_constant<is_trivially_copyable_v<T>>{});
170+
171+
return *this;
172+
}
173+
// Swap contents with another buffer.
174+
void swap(self_type& rhs) noexcept(noexcept(swap_impl(rhs, bool_constant<is_trivially_copyable_v<T>>{}))) {
175+
swap_impl(rhs, bool_constant<is_trivially_copyable_v<T>>{});
176+
}
177+
// Append an element, overwriting when configured.
151178
template<typename U>
152179
void push_back(U&& value) {
153180
push_back(std::forward<U>(value), bool_constant<Overwrite>{});
154181
}
182+
// Remove the oldest element if present.
155183
void pop_front() noexcept{
156184
if(empty())
157185
return;
@@ -161,36 +189,52 @@ using std::bool_constant;
161189
--size_;
162190
tail_ = ++tail_ %N;
163191
}
192+
// Access the newest element.
164193
[[nodiscard]] reference back() noexcept {
165194
return reinterpret_cast<reference>(elements_[(head_ + N - 1) % N]);
166195
}
196+
// Access the newest element (const).
167197
[[nodiscard]] const_reference back() const noexcept {
168198
return const_cast<self_type*>(this)->back();
169199
}
200+
// Access the oldest element.
170201
[[nodiscard]] reference front() noexcept { return reinterpret_cast<reference >(elements_[tail_]); }
202+
// Access the oldest element (const).
171203
[[nodiscard]] const_reference front() const noexcept {
172204
return const_cast<self_type*>(this)->front();
173205
}
206+
// Direct access by internal storage index.
174207
[[nodiscard]] reference operator[](size_type index) noexcept {
175208
return reinterpret_cast<reference >(elements_[index]);
176209
}
210+
// Direct access by internal storage index (const).
177211
[[nodiscard]] const_reference operator[](size_type index) const noexcept {
178212
return const_cast<self_type *>(this)->operator[](index);
179213
}
214+
// Iterator to oldest element.
180215
[[nodiscard]] iterator begin() noexcept { return iterator{this, tail_, 0};}
216+
// Iterator to one past newest element.
181217
[[nodiscard]] iterator end() noexcept { return iterator{this, head_, size_};}
218+
// Const iterator to oldest element.
182219
[[nodiscard]] const_iterator cbegin() const noexcept { return const_iterator{this, tail_, 0};}
220+
// Const iterator to one past newest element.
183221
[[nodiscard]] const_iterator cend() const noexcept { return const_iterator{this, head_, size_};}
222+
// Check if buffer has no elements.
184223
[[nodiscard]] bool empty() const noexcept { return size_ == 0; }
224+
// Check if buffer is at capacity.
185225
[[nodiscard]] bool full() const noexcept { return size_ == N; }
226+
// Current element count.
186227
[[nodiscard]] size_type size() const noexcept { return size_; }
228+
// Maximum element count.
187229
[[nodiscard]] size_type capacity() const noexcept { return N; }
230+
// Remove all elements and reset indices.
188231
void clear() noexcept {
189232
destroy_all(bool_constant<is_trivially_destructible_v<value_type>>{});
190233
size_ = 0;
191234
head_ = 0;
192235
tail_ = 0;
193236
}
237+
// Destroy elements on teardown.
194238
~ring_buffer() {
195239
clear();
196240
};
@@ -204,7 +248,7 @@ using std::bool_constant;
204248
}
205249
}
206250
void copy_impl(self_type const& rhs, std::true_type) {
207-
std::memcpy(elements_, rhs.elements_, rhs.size_ * sizeof(T));
251+
std::memcpy(elements_, rhs.elements_, N * sizeof(T));
208252
size_ = rhs.size_;
209253
tail_ = rhs.tail_;
210254
head_ = rhs.head_;
@@ -248,6 +292,59 @@ using std::bool_constant;
248292

249293
#endif
250294
}
295+
void move_impl(self_type& rhs, std::true_type) {
296+
std::memcpy(elements_, rhs.elements_, N * sizeof(T));
297+
size_ = rhs.size_;
298+
tail_ = rhs.tail_;
299+
head_ = rhs.head_;
300+
rhs.clear();
301+
}
302+
void move_impl(self_type& rhs, std::false_type) {
303+
tail_ = rhs.tail_;
304+
head_ = rhs.head_;
305+
size_ = rhs.size_;
306+
#ifdef __cpp_exceptions
307+
try {
308+
for (auto i = 0; i < size_; ++i)
309+
new( elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N]));
310+
}catch(...) {
311+
while(!empty()) {
312+
destroy(tail_, bool_constant<std::is_trivially_destructible_v<value_type>>{});
313+
tail_ = ++tail_ % N;
314+
--size_;
315+
}
316+
throw;
317+
}
318+
#else
319+
storage_type *p = nullptr;
320+
for (auto i = 0; i < size_; ++i) {
321+
p =reinterpret_cast<storage_type *>(new(elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N])));
322+
if (!p) {
323+
break;
324+
}
325+
}
326+
if (!p) {
327+
while(!empty()) {
328+
destroy(tail_, bool_constant<is_trivially_destructible_v<value_type>>{});
329+
tail_ = ++tail_ % N;
330+
--size_;
331+
}
332+
}
333+
#endif
334+
rhs.clear();
335+
}
336+
void swap_impl(self_type& rhs, std::true_type) noexcept {
337+
std::swap(elements_, rhs.elements_);
338+
std::swap(head_, rhs.head_);
339+
std::swap(tail_, rhs.tail_);
340+
std::swap(size_, rhs.size_);
341+
}
342+
void swap_impl(self_type& rhs, std::false_type) {
343+
self_type temp;
344+
temp.move_impl(*this, std::false_type{});
345+
this->move_impl(rhs, std::false_type{});
346+
rhs.move_impl(temp, std::false_type{});
347+
}
251348
template<typename U>
252349
void push_back(U&& value, std::true_type) {
253350
push_back_impl(std::forward<U>(value));
@@ -284,5 +381,10 @@ using std::bool_constant;
284381
size_type size_{};
285382
};
286383

384+
template<typename T, size_t N, bool Overwrite>
385+
void swap(ring_buffer<T, N, Overwrite>& lhs, ring_buffer<T, N, Overwrite>& rhs) noexcept(noexcept(lhs.swap(rhs))) {
386+
lhs.swap(rhs);
387+
}
388+
287389
}
288390
#endif //RINGBUFFERTEST_RINGBUFFER_HPP

0 commit comments

Comments
 (0)