In this recipe, we will learn how to create and use a ring buffer on top of a C++ array:
- In your working ~/test directory, create a subdirectory called ringbuf.
- Use your favorite text editor to create a ringbuf.cpp file in the ringbuf subdirectory.
- Define the RingBuffer class, starting from the private data fields:
#include <iostream>
template<class T, size_t N>
class RingBuffer {
private:
T objects[N];
size_t read;
size_t write;
size_t queued;
public:
RingBuffer(): read(0), write(0), queued(0) {}
- Now we add a method to push data to the buffer:
T& push() {
T& current = objects[write];
write = (write + 1) % N;
queued++;
if (queued > N) {
queued = N;
read = write;
}
return current;
}
- Next, we add a method to pull data from the buffer:
const T& pull() {
if (!queued) {
throw std::runtime_error("No data in the ring buffer");
}
T& current = objects[read];
read = (read + 1) % N;
queued--;
return current;
}
- Let's add a small method to check whether the buffer contains any data and wrap up the class definition:
bool has_data() {
return queued != 0;
}
};
- With RingBuffer defined, we can now add code that uses it. Firstly, let's define the data type we are going to use:
struct Frame {
uint32_t index;
uint8_t data[1024];
};
- Secondly, add the main function and define an instance of RingBuffer as its variable, along with code that tries to work with an empty buffer:
int main() {
RingBuffer<Frame, 10> frames;
std::cout << "Frames " << (frames.has_data() ? "" : "do not ")
<< "contain data" << std::endl;
try {
const Frame& frame = frames.pull();
} catch (std::runtime_error e) {
std::cout << "Exception caught: " << e.what() << std::endl;
}
- Next, add code that works with five elements in the buffer:
for (size_t i = 0; i < 5; i++) {
Frame& out = frames.push();
out.index = i;
out.data[0] = 'a' + i;
out.data[1] = '\0';
}
std::cout << "Frames " << (frames.has_data() ? "" : "do not ")
<< "contain data" << std::endl;
while (frames.has_data()) {
const Frame& in = frames.pull();
std::cout << "Frame " << in.index << ": " << in.data << std::endl;
}
- After that, add similar code that deals with a larger number of elements that can be added:
for (size_t i = 0; i < 26; i++) {
Frame& out = frames.push();
out.index = i;
out.data[0] = 'a' + i;
out.data[1] = '\0';
}
std::cout << "Frames " << (frames.has_data() ? "" : "do not ")
<< "contain data" << std::endl;
while (frames.has_data()) {
const Frame& in = frames.pull();
std::cout << "Frame " << in.index << ": " << in.data << std::endl;
}
}
- Create a file called CMakeLists.txt in the loop subdirectory with the following content:
cmake_minimum_required(VERSION 3.5.1)
project(ringbuf)
add_executable(ringbuf ringbuf.cpp)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
SET(CMAKE_CXX_FLAGS "--std=c++11")
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabi-g++)
- Build the application and copy the resulting executable binary to the target system. Use recipes from Chapter 2, Setting Up the Environment, to do it.
- Switch to the target system terminal. Log in using user credentials, if needed.
- Run the binary.