-
Notifications
You must be signed in to change notification settings - Fork 0
Enhance ring buffer implementation with additional features and tests #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- 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.
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughAdds copy/move/swap semantics, richer element accessors and iterator state to RingBuffer; introduces an INTERFACE Changes
Sequence Diagram(s)(Skipped.) Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Important
Looks good to me! 👍
Reviewed everything up to 002c14f in 2 minutes and 21 seconds. Click for details.
- Reviewed
587lines of code in4files - Skipped
1files when reviewing. - Skipped posting
7draft comments. View those below. - Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. CMakeLists.txt:16
- Draft comment:
Good use of the RINGBUFFER_BUILD_TESTS option and FetchContent integration for googletest. - Reason this comment was not posted:
Confidence changes required:0%<= threshold50%None
2. README.md:61
- Draft comment:
The FetchContent integration instructions and testing policy updates are clear and helpful. - Reason this comment was not posted:
Confidence changes required:0%<= threshold50%None
3. RingBuffer.hpp:95
- Draft comment:
Improved iterator equality now compares source, count, and index—this enhances correctness when comparing iterators from different buffers. - Reason this comment was not posted:
Confidence changes required:0%<= threshold50%None
4. RingBuffer.hpp:337
- Draft comment:
Swapping the C-style array 'elements_' using std::swap may not work as intended. Consider using std::swap_ranges (or wrapping the storage in a std::array) to ensure all elements are correctly exchanged. - Reason this comment was not posted:
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% The comment is about swapping C-style arrays. In C++,std::swapon C-style arrays actually DOES work correctly since C++11 - it swaps the entire array element-by-element. The standard library has a specialization for arrays. So the comment appears to be incorrect. The codestd::swap(elements_, rhs.elements_)should work fine for swapping two C-style arrays of the same type and size. The suggestion to usestd::swap_rangesis unnecessary. This appears to be a case where the automated tool is not aware thatstd::swaphandles arrays correctly in modern C++. I should double-check my understanding ofstd::swapwith C-style arrays. While I believe it works correctly in C++11 and later, I want to make sure I'm not missing something. Also, the code is trying to maintain C++14 compatibility (based on the#if (__cplusplus < 201703L)checks), so I should verify this works in C++14 as well. After reconsidering, I'm confident thatstd::swaphas been able to handle C-style arrays correctly since C++11 via template specialization. The standard library providestemplate<class T, size_t N> void swap(T (&a)[N], T (&b)[N])which swaps arrays element-by-element. Since this codebase appears to target at least C++14 (based on the version checks), this functionality is available. The comment is incorrect. The comment should be deleted because it's based on incorrect information.std::swapworks correctly with C-style arrays in C++11 and later, which this codebase supports. The suggested change tostd::swap_rangesis unnecessary and would not improve the code.
5. test_main.cpp:240
- Draft comment:
Tests are comprehensive; consider adding cases for move‐only types and exception safety for non‐trivial types to further validate move semantics. - Reason this comment was not posted:
Confidence changes required:33%<= threshold50%None
6. RingBuffer.hpp:201
- Draft comment:
Typo: There's an extra space inside the angle brackets inreinterpret_cast<reference >(elements_[tail_]). Consider removing the space to be consistent with other casts (i.e.reinterpret_cast<reference>(elements_[tail_])). - Reason this comment was not posted:
Comment was on unchanged code.
7. RingBuffer.hpp:208
- Draft comment:
Typo: There's an extra space inside the angle brackets inreinterpret_cast<reference >(elements_[index]). It should bereinterpret_cast<reference>(elements_[index]). - Reason this comment was not posted:
Comment was on unchanged code.
Workflow ID: wflow_iQtjd5IsoY5a2LE9
You can customize by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 002c14f8d3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }catch(...) { | ||
| while(!empty()) { | ||
| destroy(tail_, bool_constant<std::is_trivially_destructible_v<value_type>>{}); | ||
| tail_ = ++tail_ % N; | ||
| --size_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Track constructed elements when move throws
If T's move constructor throws during move_impl (non-trivial path), the catch block destroys elements in a loop driven by size_, which was set to rhs.size_ before construction. When the throw happens partway through, only some slots were constructed, so this loop can call destroy on uninitialized storage, which is undefined behavior and can crash for types with throwing moves. This can surface in move construction, move assignment, and swap_impl for such types. Consider tracking the number of successfully constructed elements (or updating size_ incrementally) and only destroying those.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR enhances the ring buffer implementation with move semantics, swap functionality, iterator improvements, and comprehensive testing. It also updates the build system to create a proper CMake interface library and adds documentation for FetchContent integration.
Changes:
- Added move constructor, move assignment operator, and swap member/non-member functions to RingBuffer
- Enhanced iterator equality operators to compare source buffer in addition to position
- Updated CMakeLists.txt to create an interface library target with optional test building
- Added 14 new comprehensive unit tests covering edge cases, move semantics, and swap operations
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| RingBuffer.hpp | Added move semantics, swap functionality, improved iterator comparison, and inline documentation comments |
| test_main.cpp | Added comprehensive tests for copy behavior, iterator equality, move/swap operations, and edge cases |
| CMakeLists.txt | Restructured to create RingBuffer interface library with conditional test building |
| README.md | Updated repository URLs, added FetchContent integration guide and testing policy |
| .gitignore | Added build directory and testing temporary files |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #else | ||
| storage_type *p = nullptr; | ||
| for (auto i = 0; i < size_; ++i) { | ||
| p =reinterpret_cast<storage_type *>(new(elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N]))); |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after equals sign. Should be 'p = reinterpret_cast' instead of 'p =reinterpret_cast' for consistency with code formatting standards.
RingBuffer.hpp
Outdated
| bool operator!=(ring_buffer_iterator<T,N,C,Overwrite> const& l, | ||
| ring_buffer_iterator<T,N,C,Overwrite> const& r) noexcept { | ||
| return l.count() != r.count(); | ||
| return l.source() != r.source() || l.count() != r.count() || l.index() != r.index(); |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the equality operator, the inequality operator should be based on De Morgan's law applied to the correct equality comparison. If equality should only compare source and index (not count), then inequality should be: l.source() != r.source() || l.index() != r.index()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback
| return &((*source_)[index_]); | ||
| } | ||
| [[nodiscard]] self_type& operator++() noexcept { | ||
| self_type& operator++() noexcept { |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The [[nodiscard]] attribute was removed from the prefix increment operator. This attribute is useful for preventing accidental use as a statement (e.g., ++it; when the return value is important). While prefix increment is commonly used for its side effect, having [[nodiscard]] can help catch bugs where the user meant to use the result. Consider keeping this attribute for consistency with modern C++ best practices.
| self_type& operator++() noexcept { | |
| [[nodiscard]] self_type& operator++() noexcept { |
| return *this; | ||
| } | ||
| [[nodiscard]] self_type operator++(int) noexcept { | ||
| self_type operator++(int) noexcept { |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The [[nodiscard]] attribute was removed from the postfix increment operator. This is actually less appropriate than the prefix case, as postfix increment returns a copy of the iterator before incrementing. The return value is often meaningful, and discarding it might indicate a mistake (the user should use prefix increment if they don't need the old value). Consider restoring this attribute.
| self_type operator++(int) noexcept { | |
| [[nodiscard]] self_type operator++(int) noexcept { |
| TEST(RingBufferTest, MoveConstructionTransfersState) { | ||
| ring_buffer<int, 4> b1; | ||
| b1.push_back(1); | ||
| b1.push_back(2); | ||
| b1.push_back(3); | ||
| b1.pop_front(); | ||
| b1.push_back(4); | ||
|
|
||
| ring_buffer<int, 4> b2(std::move(b1)); | ||
|
|
||
| EXPECT_TRUE(b1.empty()); | ||
| EXPECT_EQ(b2.size(), 3); | ||
| EXPECT_EQ(b2.front(), 2); | ||
| EXPECT_EQ(b2.back(), 4); | ||
| } | ||
|
|
||
| TEST(RingBufferTest, MoveAssignmentTransfersState) { | ||
| ring_buffer<int, 3> b1; | ||
| b1.push_back(5); | ||
| b1.push_back(6); | ||
|
|
||
| ring_buffer<int, 3> b2; | ||
| b2.push_back(10); | ||
| b2 = std::move(b1); | ||
|
|
||
| EXPECT_TRUE(b1.empty()); | ||
| EXPECT_EQ(b2.size(), 2); | ||
| EXPECT_EQ(b2.front(), 5); | ||
| EXPECT_EQ(b2.back(), 6); | ||
| } | ||
|
|
||
| TEST(RingBufferTest, SwapExchangesBuffers) { | ||
| ring_buffer<int, 3> b1; | ||
| b1.push_back(1); | ||
| b1.push_back(2); | ||
|
|
||
| ring_buffer<int, 3> b2; | ||
| b2.push_back(9); | ||
| b2.push_back(10); | ||
| b2.push_back(11); | ||
|
|
||
| b1.swap(b2); | ||
|
|
||
| EXPECT_EQ(b1.size(), 3); | ||
| EXPECT_EQ(b1.front(), 9); | ||
| EXPECT_EQ(b1.back(), 11); | ||
| EXPECT_EQ(b2.size(), 2); | ||
| EXPECT_EQ(b2.front(), 1); | ||
| EXPECT_EQ(b2.back(), 2); | ||
| } | ||
|
|
||
| TEST(RingBufferTest, NonMemberSwapWorks) { | ||
| ring_buffer<int, 2> b1; | ||
| b1.push_back(3); | ||
| b1.push_back(4); | ||
|
|
||
| ring_buffer<int, 2> b2; | ||
| b2.push_back(7); | ||
|
|
||
| using std::swap; | ||
| swap(b1, b2); | ||
|
|
||
| EXPECT_EQ(b1.size(), 1); | ||
| EXPECT_EQ(b1.front(), 7); | ||
| EXPECT_EQ(b2.size(), 2); | ||
| EXPECT_EQ(b2.front(), 3); | ||
| EXPECT_EQ(b2.back(), 4); | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All new move and swap tests only test trivially copyable types (int). The move_impl and swap_impl methods have separate code paths for non-trivially copyable types, but these are not tested. Consider adding tests for move construction, move assignment, and swap operations with non-trivially copyable types like std::vector or std::string to ensure both code paths work correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback
| void swap_impl(self_type& rhs, std::true_type) noexcept { | ||
| std::swap(elements_, rhs.elements_); | ||
| std::swap(head_, rhs.head_); | ||
| std::swap(tail_, rhs.tail_); | ||
| std::swap(size_, rhs.size_); | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The swap_impl for trivially copyable types attempts to swap C-style arrays (elements_), which cannot be done with std::swap. C-style arrays are not assignable. This implementation will not compile. The correct approach is to swap element-by-element or use std::swap_ranges for the arrays.
| storage_type *p = nullptr; | ||
| for (auto i = 0; i < size_; ++i) { | ||
| p =reinterpret_cast<storage_type *>(new(elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N]))); | ||
| if (!p) { | ||
| break; | ||
| } | ||
| } | ||
| if (!p) { | ||
| while(!empty()) { | ||
| destroy(tail_, bool_constant<is_trivially_destructible_v<value_type>>{}); | ||
| tail_ = ++tail_ % N; | ||
| --size_; | ||
| } | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The placement new expression will never return nullptr. Placement new either successfully constructs the object or throws an exception (if exceptions are enabled). The check 'if (!p)' will never be true because placement new always returns the pointer passed to it. This error handling logic is ineffective and should be removed.
| storage_type *p = nullptr; | |
| for (auto i = 0; i < size_; ++i) { | |
| p =reinterpret_cast<storage_type *>(new(elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N]))); | |
| if (!p) { | |
| break; | |
| } | |
| } | |
| if (!p) { | |
| while(!empty()) { | |
| destroy(tail_, bool_constant<is_trivially_destructible_v<value_type>>{}); | |
| tail_ = ++tail_ % N; | |
| --size_; | |
| } | |
| } | |
| for (auto i = 0; i < size_; ++i) { | |
| new(elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N])); | |
| } |
RingBuffer.hpp
Outdated
| bool operator==(ring_buffer_iterator<T,N,C,Overwrite> const& l, | ||
| ring_buffer_iterator<T,N,C,Overwrite> const& r) noexcept { | ||
| return l.count() == r.count(); | ||
| return l.source() == r.source() && l.count() == r.count() && l.index() == r.index(); |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The iterator equality comparison now requires all three fields (source, count, and index) to match. However, for correct iterator semantics, two iterators should be equal if they refer to the same position in the same container, regardless of their count value. The count field tracks how many times the iterator has been incremented and is not relevant for equality. This could cause issues where logically equivalent iterators (same buffer, same position) are incorrectly considered unequal if they were created through different paths. Consider comparing only source and index.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback
| TEST(RingBufferTest, IteratorsFromDifferentBuffersAreNotEqual) { | ||
| ring_buffer<int, 3> b1; | ||
| ring_buffer<int, 3> b2; | ||
|
|
||
| EXPECT_NE(b1.cbegin(), b2.cbegin()); | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test checks that iterators from different buffers are not equal, which is good. However, it only tests begin iterators from empty buffers. The test should also verify that iterators at the same logical position but from different buffers are unequal, and that iterators from the same buffer at the same position are equal. Consider adding test cases that verify: (1) begin() == begin() for the same buffer, (2) begin() != end() for the same buffer when not empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot open a new pull request to apply changes based on this feedback
Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@RingBuffer.hpp`:
- Around line 295-335: In move_impl(self_type& rhs, std::false_type) you must
not set size_ to rhs.size_ before element move-construction because a thrown
exception will cause destroy(...) to run on uninitialized slots; instead track
how many elements you actually constructed (e.g., constructed_count), leave
size_ at 0 during construction, for-loop constructing elements_ + ((tail_ + i) %
N) from rhs and increment constructed_count, on catch/detected failure destroy
only the constructed_count items (using destroy(tail_ + offset, ...)), adjust
tail_/head_ as needed while decrementing constructed_count, rethrow the
exception (or handle failure path), and only after all moves succeed set size_ =
rhs.size_ and finally rhs.clear(); apply the same constructed-count pattern in
both the __cpp_exceptions and the no-exceptions branch and reference elements_,
tail_, head_, size_, destroy, storage_type and rhs.clear() when making the
changes.
| void move_impl(self_type& rhs, std::true_type) { | ||
| std::memcpy(elements_, rhs.elements_, N * sizeof(T)); | ||
| size_ = rhs.size_; | ||
| tail_ = rhs.tail_; | ||
| head_ = rhs.head_; | ||
| rhs.clear(); | ||
| } | ||
| void move_impl(self_type& rhs, std::false_type) { | ||
| tail_ = rhs.tail_; | ||
| head_ = rhs.head_; | ||
| size_ = rhs.size_; | ||
| #ifdef __cpp_exceptions | ||
| try { | ||
| for (auto i = 0; i < size_; ++i) | ||
| new( elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N])); | ||
| }catch(...) { | ||
| while(!empty()) { | ||
| destroy(tail_, bool_constant<std::is_trivially_destructible_v<value_type>>{}); | ||
| tail_ = ++tail_ % N; | ||
| --size_; | ||
| } | ||
| throw; | ||
| } | ||
| #else | ||
| storage_type *p = nullptr; | ||
| for (auto i = 0; i < size_; ++i) { | ||
| p =reinterpret_cast<storage_type *>(new(elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N]))); | ||
| if (!p) { | ||
| break; | ||
| } | ||
| } | ||
| if (!p) { | ||
| while(!empty()) { | ||
| destroy(tail_, bool_constant<is_trivially_destructible_v<value_type>>{}); | ||
| tail_ = ++tail_ % N; | ||
| --size_; | ||
| } | ||
| } | ||
| #endif | ||
| rhs.clear(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix exception-safety in move_impl for non‑trivial types.
If a move construction throws, the current cleanup can destroy uninitialized storage because size_ is preset to rhs.size_. Track constructed count and destroy only those, then set size_ after success.
🐛 Proposed fix: track constructed elements and set size_ after success
- void move_impl(self_type& rhs, std::false_type) {
- tail_ = rhs.tail_;
- head_ = rhs.head_;
- size_ = rhs.size_;
+ void move_impl(self_type& rhs, std::false_type) {
+ tail_ = rhs.tail_;
+ size_ = 0;
`#ifdef` __cpp_exceptions
- try {
- for (auto i = 0; i < size_; ++i)
- new( elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N]));
- }catch(...) {
- while(!empty()) {
- destroy(tail_, bool_constant<std::is_trivially_destructible_v<value_type>>{});
- tail_ = ++tail_ % N;
- --size_;
- }
- throw;
- }
+ size_type constructed = 0;
+ try {
+ for (; constructed < rhs.size_; ++constructed) {
+ new(elements_ + ((tail_ + constructed) % N))
+ T(std::move(rhs[(tail_ + constructed) % N]));
+ }
+ } catch (...) {
+ while (constructed > 0) {
+ --constructed;
+ destroy((tail_ + constructed) % N,
+ bool_constant<is_trivially_destructible_v<value_type>>{});
+ }
+ head_ = tail_;
+ size_ = 0;
+ throw;
+ }
+ size_ = rhs.size_;
`#else`
- storage_type *p = nullptr;
- for (auto i = 0; i < size_; ++i) {
- p =reinterpret_cast<storage_type *>(new(elements_ + ((tail_ + i) % N)) T(std::move(rhs[(tail_ + i) % N])));
- if (!p) {
- break;
- }
- }
- if (!p) {
- while(!empty()) {
- destroy(tail_, bool_constant<is_trivially_destructible_v<value_type>>{});
- tail_ = ++tail_ % N;
- --size_;
- }
- }
+ size_type constructed = 0;
+ for (; constructed < rhs.size_; ++constructed) {
+ new(elements_ + ((tail_ + constructed) % N))
+ T(std::move(rhs[(tail_ + constructed) % N]));
+ }
+ size_ = constructed;
`#endif`
+ head_ = (tail_ + size_) % N;
rhs.clear();
}🤖 Prompt for AI Agents
In `@RingBuffer.hpp` around lines 295 - 335, In move_impl(self_type& rhs,
std::false_type) you must not set size_ to rhs.size_ before element
move-construction because a thrown exception will cause destroy(...) to run on
uninitialized slots; instead track how many elements you actually constructed
(e.g., constructed_count), leave size_ at 0 during construction, for-loop
constructing elements_ + ((tail_ + i) % N) from rhs and increment
constructed_count, on catch/detected failure destroy only the constructed_count
items (using destroy(tail_ + offset, ...)), adjust tail_/head_ as needed while
decrementing constructed_count, rethrow the exception (or handle failure path),
and only after all moves succeed set size_ = rhs.size_ and finally rhs.clear();
apply the same constructed-count pattern in both the __cpp_exceptions and the
no-exceptions branch and reference elements_, tail_, head_, size_, destroy,
storage_type and rhs.clear() when making the changes.
Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Add test coverage for move/swap operations with non-trivially copyable types
- Updated operator== to compare source and index (not count) - Updated operator!= using De Morgan's law - Modified operator++ to set index to sentinel value N when reaching end - Changed end() and cend() to use index N instead of head_ - This provides proper iterator semantics where iterators are equal if they point to the same position in the same buffer Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Modified iterator implementation to remove count from equality comparison: - Changed increment operator to not wrap index (now stores unwrapped index) - Updated dereference operators to apply modulo when accessing elements - Modified end() to use tail_ + size_ instead of head_ for unique index - Removed count from operator== and operator!= comparisons This ensures two iterators are equal if they point to the same position in the same container, regardless of iteration count, following standard iterator semantics. Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
- Changed pre-increment expression to clearer form: (index_ + 1) % N - Enhanced comment to clarify sentinel value purpose Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Removed _codeql_build_dir directory and _codeql_detected_source_root symlink that were accidentally committed. These are build artifacts from the CodeQL security scanner and should not be in version control. Updated .gitignore to prevent these files from being committed in the future. Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
Enhance iterator equality test coverage
- Removed _codeql_build_dir directory (build artifacts) - Removed _codeql_detected_source_root file - Added CodeQL artifacts to .gitignore to prevent future commits Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
[WIP] Update to address feedback on ring buffer enhancement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
RingBuffer.hpp (1)
256-294: Fix exception-safety incopy_implfor non-trivial types.This method has the same exception-safety bug as
move_impl:size_is set torhs.size_at line 259 before any elements are constructed. If a copy constructor throws mid-loop (line 265), the catch block'swhile(!empty())loop will destroy uninitialized storage—undefined behavior.Additionally, the non-exception path (lines 275-291) checks
if (!p)after placement new, but placement new never returns nullptr; it either succeeds or throws.🐛 Proposed fix: track constructed elements
void copy_impl(self_type const& rhs, std::false_type) { tail_ = rhs.tail_; - head_ = rhs.head_; - size_ = rhs.size_; + size_ = 0; `#ifdef` __cpp_exceptions + size_type constructed = 0; try { - for (auto i = 0; i < size_; ++i) - // construct value in memory of aligned storage - // using inplace operator new - new( elements_ + ((tail_ + i) % N)) T(rhs[(tail_ + i) % N]); - }catch(...) { - while(!empty()) { - destroy(tail_, bool_constant<std::is_trivially_destructible_v<value_type>>{}); - tail_ = ++tail_ % N; - --size_; + for (; constructed < rhs.size_; ++constructed) { + new(elements_ + ((tail_ + constructed) % N)) T(rhs[(tail_ + constructed) % N]); } + } catch (...) { + while (constructed > 0) { + --constructed; + destroy((tail_ + constructed) % N, + bool_constant<is_trivially_destructible_v<value_type>>{}); + } throw; } + size_ = rhs.size_; + head_ = rhs.head_; `#else` - storage_type *p = nullptr; - for (auto i = 0; i < size_; ++i) { - - // construct value in memory of aligned storage - // using inplace operator new - p =reinterpret_cast<storage_type *>(new(elements_ + ((tail_ + i) % N)) T(rhs[(tail_ + i) % N])); - if (!p) { - break; - } - } - if (!p) { - while(!empty()) { - destroy(tail_, bool_constant<is_trivially_destructible_v<value_type>>{}); - tail_ = ++tail_ % N; - --size_; - } + for (size_type i = 0; i < rhs.size_; ++i) { + new(elements_ + ((tail_ + i) % N)) T(rhs[(tail_ + i) % N]); } - + size_ = rhs.size_; + head_ = rhs.head_; `#endif` }
🤖 Fix all issues with AI agents
In `@RingBuffer.hpp`:
- Around line 342-347: The swap_impl(self_type& rhs, std::false_type)
implementation is not exception-safe when move_impl can throw; if the second or
third move_impl throws the two buffers can be left in inconsistent states. Fix
by either documenting that swap_impl only offers the basic exception guarantee
or implement a rollback: perform moves into temporaries (use a local temp for
*this and another temp for rhs), then commit by moving temporaries into targets
only after all moves succeed, or catch exceptions and restore original state by
moving back from any succeeded temporaries; update references to swap_impl,
move_impl and self_type/std::false_type accordingly so the logic uses fail-safe
temporaries and a clear commit/rollback path.
♻️ Duplicate comments (3)
RingBuffer.hpp (3)
66-74: Consider restoring[[nodiscard]]on increment operators.The
[[nodiscard]]attribute was removed from both prefix and postfix increment. For postfix increment especially, discarding the return value often indicates a mistake (prefer prefix if the old value isn't needed).
302-335: Exception-safety issue inmove_implfor non-trivial types remains unaddressed.Setting
size_ = rhs.size_before construction (line 305) means if a move constructor throws, the cleanup loop destroys uninitialized storage. The placement-new nullptr check (lines 322-324) is also ineffective.
336-341:swap_implfor trivially copyable types will not compile.
std::swap(elements_, rhs.elements_)attempts to swap C-style arrays, which are not assignable. This line will fail to compile.
🧹 Nitpick comments (1)
RingBuffer.hpp (1)
206-213: Clarifyoperator[]semantics in documentation.The
operator[]accesses elements by internal storage index rather than logical position from the oldest element. The comment says "Direct access by internal storage index" which is accurate, but users might expectbuffer[0]to return the oldest element (likefront()).Consider either:
- Renaming to a less public-API-like name (e.g.,
at_raw_index)- Adding a note that this is primarily for internal use
| void swap_impl(self_type& rhs, std::false_type) { | ||
| self_type temp; | ||
| temp.move_impl(*this, std::false_type{}); | ||
| this->move_impl(rhs, std::false_type{}); | ||
| rhs.move_impl(temp, std::false_type{}); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
swap_impl for non-trivial types lacks exception safety.
This implementation uses three move_impl calls through a temporary. If move_impl throws during the second or third move:
- Line 345: If this throws,
*thisis already cleared by line 344, andrhsis partially moved. Both buffers may be in inconsistent states. - Line 346: If this throws,
*thishasrhs's old data, butrhsis cleared andtempstill holds*this's original data.
The strong exception guarantee cannot be provided with this approach when moves can throw. Consider documenting that swap provides only basic exception guarantee, or implement a more careful rollback mechanism.
🤖 Prompt for AI Agents
In `@RingBuffer.hpp` around lines 342 - 347, The swap_impl(self_type& rhs,
std::false_type) implementation is not exception-safe when move_impl can throw;
if the second or third move_impl throws the two buffers can be left in
inconsistent states. Fix by either documenting that swap_impl only offers the
basic exception guarantee or implement a rollback: perform moves into
temporaries (use a local temp for *this and another temp for rhs), then commit
by moving temporaries into targets only after all moves succeed, or catch
exceptions and restore original state by moving back from any succeeded
temporaries; update references to swap_impl, move_impl and
self_type/std::false_type accordingly so the logic uses fail-safe temporaries
and a clear commit/rollback path.
- Removed % N from operator* and operator-> since index_ is always in [0, N-1] for valid iterators - Changed end() and cend() to use N as sentinel value, consistent with operator++ implementation - With the current operator++ that keeps index_ bounded and sets it to N at end, the modulo in dereference operators is redundant Co-authored-by: bugparty <1510776+bugparty@users.noreply.github.com>
[WIP] Update ring buffer enhancement based on review feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
RingBuffer.hpp (2)
186-194: Undefined behavior in C++14: unsequenced modification.The expression
tail_ = ++tail_ % Non line 194 has two modifications totail_that are unsequenced in C++14 and earlier, causing undefined behavior. Since the code includes C++14 backports (lines 109-124), this is a compatibility concern.This pattern appears in multiple locations: lines 194, 250, 273, 292, 317, 333, 369, 372.
🐛 Proposed fix for line 194
- tail_ = ++tail_ %N; + tail_ = (tail_ + 1) % N;Apply the same fix pattern to all other occurrences.
260-297: Exception-safety issue: same pattern asmove_impl.This function has the same exception-safety flaw as
move_impl:size_is set torhs.size_on line 263 before any elements are constructed. If construction throws partway through, the cleanup loop will calldestroyon uninitialized storage.Additionally, the placement new
nullptrcheck (lines 285-295) is ineffective since placement new never returnsnullptr.🐛 Proposed fix: track constructed count
void copy_impl(self_type const& rhs, std::false_type) { tail_ = rhs.tail_; - head_ = rhs.head_; - size_ = rhs.size_; + size_ = 0; `#ifdef` __cpp_exceptions + size_type constructed = 0; try { - for (auto i = 0; i < size_; ++i) - // construct value in memory of aligned storage - // using inplace operator new - new( elements_ + ((tail_ + i) % N)) T(rhs[(tail_ + i) % N]); - }catch(...) { - while(!empty()) { - destroy(tail_, bool_constant<std::is_trivially_destructible_v<value_type>>{}); - tail_ = ++tail_ % N; - --size_; + for (; constructed < rhs.size_; ++constructed) { + new(elements_ + ((tail_ + constructed) % N)) T(rhs[(tail_ + constructed) % N]); } + } catch (...) { + while (constructed > 0) { + --constructed; + destroy((tail_ + constructed) % N, bool_constant<is_trivially_destructible_v<value_type>>{}); + } throw; } + size_ = rhs.size_; + head_ = rhs.head_; `#else` - storage_type *p = nullptr; - for (auto i = 0; i < size_; ++i) { - - // construct value in memory of aligned storage - // using inplace operator new - p =reinterpret_cast<storage_type *>(new(elements_ + ((tail_ + i) % N)) T(rhs[(tail_ + i) % N])); - if (!p) { - break; - } - } - if (!p) { - while(!empty()) { - destroy(tail_, bool_constant<is_trivially_destructible_v<value_type>>{}); - tail_ = ++tail_ % N; - --size_; - } + for (size_type i = 0; i < rhs.size_; ++i) { + new(elements_ + ((tail_ + i) % N)) T(rhs[(tail_ + i) % N]); } - + size_ = rhs.size_; + head_ = rhs.head_; `#endif` }
♻️ Duplicate comments (4)
RingBuffer.hpp (4)
66-78: Consider restoring[[nodiscard]]on increment operators.The
[[nodiscard]]attribute was removed from both prefix and postfix increment operators. For postfixoperator++(int), discarding the return value often indicates the programmer should use prefix increment instead. Consider restoring this attribute to catch potential misuse.
299-338: Exception-safety and code issues remain unaddressed.The previous review flagged critical issues in
move_implthat are still present:
- Exception safety (lines 306-321):
size_is set before construction; if a move throws,destroyis called on uninitialized storage.- Ineffective null check (lines 323-336): Placement new never returns
nullptr, making this error handling dead code.- Formatting (line 325): Missing space after
=inp =reinterpret_cast.Please refer to the previous review comments for the detailed fix.
340-345:std::swapon C-style arrays will not compile.Line 341 attempts
std::swap(elements_, rhs.elements_), but C-style arrays are not assignable and cannot be swapped directly withstd::swap. This code will fail to compile whenswap_implis instantiated for trivially copyable types.🐛 Proposed fix: swap element-by-element or use std::swap_ranges
void swap_impl(self_type& rhs, std::true_type) noexcept { - std::swap(elements_, rhs.elements_); + for (size_type i = 0; i < N; ++i) { + std::swap(elements_[i], rhs.elements_[i]); + } std::swap(head_, rhs.head_); std::swap(tail_, rhs.tail_); std::swap(size_, rhs.size_); }#!/bin/bash # Verify that std::swap on C-style arrays fails to compile cat > /tmp/test_swap.cpp << 'EOF' `#include` <utility> `#include` <algorithm> struct alignas(int) storage_t { char data[sizeof(int)]; }; int main() { storage_t a[5]{}; storage_t b[5]{}; std::swap(a, b); // Should fail: arrays are not assignable return 0; } EOF g++ -std=c++17 -c /tmp/test_swap.cpp 2>&1 || echo "Compilation failed as expected"
346-351:swap_implfor non-trivial types provides only basic exception guarantee.As flagged previously, if
move_implthrows during the second or third move operation, both buffers may be left in inconsistent states. Consider documenting that swap provides only the basic exception guarantee for types with throwing move constructors.
🧹 Nitpick comments (1)
RingBuffer.hpp (1)
196-217: Consider documenting preconditions for element accessors.
back(),front(), andoperator[]do not check if the buffer is empty or if the index is valid. This is consistent with standard library containers, but the undefined behavior on empty buffers should be documented for users.
Important
Enhance
RingBufferwith move semantics, swap functionality, iterator improvements, and add comprehensive tests.ring_bufferinRingBuffer.hpp.ring_buffer_iteratorinRingBuffer.hpp.CMakeLists.txtto conditionally include Google Test based onRINGBUFFER_BUILD_TESTSoption.RingBufferas an interface library inCMakeLists.txt.README.mdwith FetchContent integration instructions and testing policy.test_main.cppfor move semantics, swap functionality, iterator equality, and buffer state after operations.This description was created by
for 002c14f. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.