Project: STL-compatible ring buffer
Some time ago I needed a ring buffer for a work thing, and made the very bad choice to put one together in my spare time. "Surely there are dozens of existing implementations", I hear you think, but I'm picky: I wanted it to live alongside the standard library, and perform about as well as a typical implementation thereof. Boost has one, but I didn't want to be the one to pull that into the project. And so it has come to this:
# [baudvine::RingBuf](https://github.com/barometz/ringbuf)
A broadly STL-compatible ring buffer with good performance and a decent test suite. It's header-only, builds with C++11 and up, and I've made an effort to keep it readable after spending too much time reading STL implementations.
From [examples.cpp](https://github.com/barometz/ringbuf/blob/main/test/examples.cpp), lightly abridged:
TEST(Example, PushPop) { baudvine::RingBuf<std::string, 3> buffer;
// A circular buffer lets you keep adding elements, but once it exceeds its // configured size (3, in this case) the oldest one gets removed. buffer.push_back("line 1"); EXPECT_EQ(buffer.front(), "line 1");
buffer.push_back("another line"); EXPECT_THAT(buffer, testing::ElementsAre("line 1", "another line"));
buffer.push_back("and they just keep coming"); // So once you push the fourth element to the back... buffer.push_back("and they won't stop coming"); // The first element falls out. EXPECT_THAT(buffer, testing::ElementsAre("another line", "and they just keep coming", "and they won't stop coming")); }
TEST(Example, RangeFor) { baudvine::RingBuf<int, 3> buffer;
buffer.push_back(5); buffer.push_back(4); buffer.push_back(3);
int expected = 5; for (auto& value : buffer) { EXPECT_EQ(value, expected); expected--; } }
It works pretty much like an STL container, with some effort put into actually matching the Container named requirement. The iterator implementation is *probably* not entirely compliant yet, but it works fine.
As with e.g. std::vector, allocation and element lifetime are separate (see the [RefCounter tests](https://github.com/barometz/ringbuf/blob/main/test/test_ringbuf.cpp#L200)) so the memory can be contiguous while the `pop_` member functions do actually destroy elements. I intend to support custom allocators at some point; right now it's all `std::allocator`.
Also included is a custom `copy` function to take advantage of the mostly-contiguous nature of the container - it works out to be about twice as fast as using std::copy (but both are on the order of tens of milliseconds with 33M `int`s).
I truly hope this is useful for someone, but I'm also interested in feedback if anyone has any - all the more so because this has led me down a few paths I haven't had to deal with in my day to day.