DMarlon Studio

Lesson 10 · 10 of 20

Class templates · C++

Build a generic ring buffer with a C++ class template, with the type and the size as parameters: sensor readings and recently opened files.

C++ · Class templates in DMarlon Studio: the example's code and its output in the terminal

A class template does for classes what a function template does for functions: RingBuffer<T, N> is a mould and RingBuffer<double, 4> is an actual class. That is how std::vector<T> and std::map<K, V> are made.

The second parameter, std::size_t N, is not a type but a value fixed at compile time. That is why it can be the size of std::array<T, N>, a fixed-size array that lives inside the object, with no dynamic memory. The braces in data_{} fill it with zeros (or empty strings).

It is a ring buffer: push writes at position next_ and moves on with % N, which wraps back to 0 at the end. Once it is full, each new value overwrites the oldest, so of the six temperatures only the last four remain. It is handy for keeping a sensor's latest readings or a history of recently opened files.

operator[] maps the logical position (0 is the oldest) to the real one inside the array, and show uses it as (*this)[i]. The same code works for any T that can be printed with <<: double in temps and std::string in recent. With one file, recent shows [1/3]; after four, it keeps the last three.

Code · main.cpp

#include <array>#include <iostream>#include <string>// A class template: T is the element type, N a size fixed at compile time.template <typename T, std::size_t N>class RingBuffer {    std::array<T, N> data_{};    std::size_t next_ = 0, count_ = 0;public:    void push(const T& value) {                 // when full, overwrites the oldest        data_[next_] = value;        next_ = (next_ + 1) % N;        if (count_ < N) ++count_;    }    std::size_t size() const { return count_; }    const T& operator[](std::size_t i) const { return data_[(next_ + N - count_ + i) % N]; }   // 0 = oldest    void show(const std::string& label) const {        std::cout << label << " [" << count_ << "/" << N << "]:";        for (std::size_t i = 0; i < count_; ++i) std::cout << " " << (*this)[i];        std::cout << "\n";    }};int main() {    RingBuffer<double, 4> temps;                // the last 4 sensor readings    for (double t : {18.5, 19.0, 21.2, 22.8, 23.1, 20.4}) temps.push(t);    temps.show("Temperatures");    RingBuffer<std::string, 3> recent;          // the last 3 files opened    recent.push("main.cpp");    recent.show("Recent files");    for (auto f : {"shape.h", "README.md", "Makefile"}) recent.push(f);    recent.show("Recent files");}

Output · c++ -std=c++17 main.cpp -o main && ./main

Temperatures [4/4]: 21.2 22.8 23.1 20.4
Recent files [1/3]: main.cpp
Recent files [3/3]: shape.h README.md Makefile

Key points

  • template <typename T, std::size_t N> class ... defines a class template.
  • You supply the arguments when using it: RingBuffer<double, 4>.
  • A parameter can be a constant value, such as a size.
  • Each combination of arguments is a separate class.
  • std::array<T, N> is a fixed-size array with no dynamic memory.

Exercise

Add an average() method that returns the mean of the stored elements (for numeric types) and use it to print the average of the last four temperature readings.