Mini Project 8 — Generic Ring Buffer¶
Difficulty: Intermediate · Estimated effort: 4–5 hours · Prerequisites: Module 12 — Templates, Module 10 — Copying and Resource Management · Combines: class templates, a non-type parameter, value semantics, an iterator-lite interface
No complete solution is provided. You are writing a container, so the standard library is your reference for the interface — not for the code.
1. Scenario¶
A data logger on a bench records readings continuously, but the bench only wants the most
recent n. The logger's author wrote int-only code with a fixed array, and needs the same
buffer for double readings and for LogEntry records — without writing the wrap-around logic
three times.
2. Problem statement¶
Build a RingBuffer<T, N> template that keeps the last N items pushed into it, with a
compile-time capacity, correct wrap-around behaviour, and value semantics a reader can predict.
3. User requirements¶
- [ ] Push an item; when the buffer is full, the oldest item is overwritten.
- [ ] Ask how many items are held and what the capacity is.
- [ ] Read an item by position, oldest first.
- [ ] Copy the buffer and get an independent one.
- [ ] Instantiate it for numbers, strings and one of your own types.
4. Functional requirements¶
- [ ]
template <typename T, std::size_t N> class RingBufferwith static storage. - [ ]
push(const T&)andpush(T&&), with move used when it should be. - [ ]
size(),capacity(),empty(),full(),clear(). - [ ]
operator[](std::size_t)andat(std::size_t);atreports an out-of-range index. - [ ]
front()andback()accessors, bothconstand non-const. - [ ] Copy assignment must copy the logical order, not the underlying array order.
5. Non-functional requirements¶
- [ ] Capacity is a template parameter; no dynamic allocation for the storage.
- [ ]
RingBuffer<T, 0>must either be refused at compile time with a clear message or behave as documented; silence is not acceptable. - [ ] Instantiated for at least three types, including one non-trivial type, before submitting.
- [ ] Builds warning-free with
g++ -std=c++17 -Wall -Wextra -Werror. - [ ] A template definition lives in a header; a separate
.cppthat is never instantiated in that translation unit is the error this constraint exists to prevent.
6. Suggested features¶
- A
for_eachor range-for-style visit taking a callable, oldest first. begin()/end()returning a simple forward iterator — deliberately hard; do it only if the rest is finished.- A
to_vector()snapshot, with a note on what it costs. - A
static_assertgiving a friendly message for an unsupported type.
7. OOP concepts involved¶
| Concept | Where it appears |
|---|---|
| Class templates | One container, many element types |
| Non-type template parameter | Capacity as part of the type, checked at compile time |
| Encapsulation | Head, count and wrap-around arithmetic are private and consistent |
| Value semantics | Copy and move are defined in terms of logical content |
| RAII | Storage belongs to the object; no manual cleanup |
static_assert |
Diagnostics at compile time instead of behaviour at run time |
8. Suggested design considerations¶
Separate logical position from physical position. operator[](0) means oldest. Keeping
that true for every operation is most of the work, and hiding it behind one private helper is
most of the design.
Write the copy semantics carefully. A member-wise copy of head, count and the array is
usually right if the physical order is preserved — but a copy made mid-wrap-around must still
answer operator[](0) correctly. Test it after N + 1 pushes.
Non-trivial types cost you care. With std::string or your own type, overwriting a full
slot must assign, not skip destruction. If you reach for placement new or memcpy, stop and
ask whether that was necessary — it is not.
Decide what N == 0 means. A zero-capacity buffer is a design hole: either static_assert
it away with a readable message, or define every query's answer. Both are acceptable; a crash is
not.
9. Minimum viable version¶
A working RingBuffer<T, N> for int with push, size, capacity, operator[], front,
back and clear, tested by pushing 2 * N values and checking the retained ones.
10. Intermediate extension¶
Add copy and move operations, then instantiate the buffer for std::string and one of your own
classes. Verify that a copy made after N + 1 pushes has the same logical order as the
original.
11. Advanced extension¶
Add a visitor taking a callable and use it to compute a maximum and a running average. Then add
begin()/end() — oldest to newest, exact — and write down what the iterator had to know that
the rest of the class does not, which is the honest cost of the feature.
12. Testing checklist¶
- [ ] Fewer than
Npushes:sizeis the number pushed, order is insertion order - [ ] Exactly
Npushes: full,frontis the first pushed - [ ]
N + 1pushes: the first pushed is gone,frontis the second - [ ]
2 * Npushes: contents are the lastN, in order - [ ]
at(N)reports an out-of-range error;operator[]is not required to - [ ] A copy after wrapping has identical logical order
- [ ] Instantiation for
std::stringcompiles and behaves the same way - [ ]
clear()leavesempty()true andcapacity()unchanged
13. Documentation checklist¶
- [ ] The header states the invariant:
count <= N, and position mapping - [ ] Why capacity is a template parameter rather than a constructor argument
- [ ] The
N == 0rule, whether it is astatic_assertor defined behaviour - [ ] Which operations are
noexceptand why - [ ] A short usage example in the header comment
14. Self-evaluation rubric¶
| Criterion | Excellent looks like | Weight |
|---|---|---|
| Correctness | Logical order correct before, at and after wrap-around | 30% |
| Template design | Clean interface; capacity in the type; no ODR problems | 25% |
| Value semantics | Copy and move correct mid-wrap; no leaks or double frees | 20% |
| Diagnostics | N == 0 and unsupported uses give readable messages |
10% |
| Encapsulation | Mapping from logical to physical index in one place | 5% |
| Documentation | Invariant and design decisions written down | 10% |
Related: Mini projects · Project roadmap · All projects · Module 12 — Templates