2019/08/28

C++でリングバッファを実装

背景


仕事でプログラミングする際に、リングバッファを多用するため、テンプレートを作成する

記事の目的


リングバッファのテンプレートを作成する

リングバッファ


ここでは、リングバッファの記述方法について記載する。

仕様

テンプレートの仕様について記載する。
  • 複数のスレッドから、バッファサイズの変更、バッファの書き込み、バッファの読み込みが可能
  • データが格納されていないバッファにアクセスすると、エラーが表示され、デフォルトのデータが返される

テンプレート

リングバッファを使用するテンプレートを記載する。
// ring_buffer.cpp
#include <stdio.h>
#include <mutex>

// リングバッファに格納するデータ形式
struct Data
{
    int number = 0;
};

// 前方宣言
class RingBuffer
{
    public:
        // コンストラクタ(リングバッファのサイズを指定(2の冪乗が最適))
        RingBuffer(uint buffer_size_in);
        // デストラクタ
        ~RingBuffer();
        // リングバッファのサイズを指定(0より大の整数、2の冪乗が最適)
        void setBufferSize(uint buffer_size_in);
        // リングバッファのサイズを取得
        uint getBufferSize();
        // リングバッファにデータを書き込み
        void writeBuffer(Data data_in);
        // リングバッファのデータを読み込み(0: 最新, BufferSize: 最古)
        Data readBuffer(uint read_pointer_in);
    private:
        // コンストラクタ(宣言時に必ずバッファサイズを宣言する)
        RingBuffer();
        // リングバッファのポインタ
        Data *data_;
        // リングバッファのサイズ
        uint buffer_size_   = 0;
        // リングバッファ内に書き込まれたデータ数
        uint data_size_     = 0;
        // 書き込みポインタ(最新データのポインタ)
        uint write_pointer_ = 0;
        // mutex(読み書き、バッファサイズ変更を排他)
        std::mutex mtx_;
};
// コンストラクタ(プライベート関数)
RingBuffer::RingBuffer()
{}
// コンストラクタ(リングバッファのサイズを指定)
RingBuffer::RingBuffer(uint buffer_size_in)
{
    // リングバッファのサイズを指定、メモリ上にバッファを確保
    setBufferSize(buffer_size_in);
}
// デストラクタ(リングバッファを削除)
RingBuffer::~RingBuffer()
{
    if (buffer_size_ > 0)
    {
        delete[] data_;
    }
}
// リングバッファのサイズを指定、メモリ上にバッファを確保
void RingBuffer::setBufferSize(uint buffer_size_in)
{
    if (buffer_size_in > 0)
    {
        // 排他
        std::lock_guard<std::mutex> lock(mtx_);
        // すでにメモリ上にリングバッファが確保されている場合は、一旦削除
        if (buffer_size_ > 0)
        {
            delete data_;
        }
        // リングバッファのサイズを保持
        buffer_size_ = buffer_size_in;
        // 内部変数の初期化
        data_size_   = 0;
        write_pointer_   = 0;
        // メモリ上にリングバッファを確保
        data_ = new Data[buffer_size_in];
    }
    else
    {
        fprintf(stderr, "Error: [RingBuffer] Please set buffer size more than 0.\n");
    }
}
// リングバッファのサイズを取得
uint RingBuffer::getBufferSize()
{
    // 排他
    std::lock_guard<std::mutex> lock(mtx_);
    return buffer_size_;
}
// リングバッファにデータ書き込み
void RingBuffer::writeBuffer(Data data_in)
{
    // リングバッファが確保されている場合のみ処理
    if (buffer_size_ > 0)
    {
        // 排他
        std::lock_guard<std::mutex> lock(mtx_);
        // 書き込みポインタを進める
        write_pointer_ ++;
        write_pointer_ %= buffer_size_;
        // データを書き込み
        data_[write_pointer_] = data_in;
        // 書き込んだデータ数を保持
        if (data_size_ < buffer_size_)
        {
            data_size_ ++;
        }
    }
    else
    {
        fprintf(stderr, "Error: [RingBuffer] Buffer is not allocated.\n");
    }
}

Data RingBuffer::readBuffer(uint read_pointer_in)
{
    // 過去にデータが書き込まれていないデータのポインタにはアクセスさせない
    if (read_pointer_in < data_size_)
    {
        // 排他
        std::lock_guard<std::mutex> lock(mtx_);
        // 現在の最新データのポインタを0, 古いほど値が大きくなる(最大: Buffer Size)
        uint read_pointer = (buffer_size_+write_pointer_-read_pointer_in)%buffer_size_;
        return data_[read_pointer] ;
    }
    else
    {
        fprintf(stderr, "Error: [RingBuffer] Read pointer is out of buffer. (%d / %d)\n",
                            read_pointer_in, data_size_);
        Data data;
        return data;
    }
}

// テストコード
int main()
{
    fprintf(stdout, "Error pattern -------\n"); 
    // 宣言(エラー)
    RingBuffer buf(0);
    Data data_1;
    data_1.number = 0;
    // データ書き込み(エラー)
    buf.writeBuffer(data_1);
    // データ読み込み(エラー)
    buf.readBuffer(1);
    // リングバッファのサイズを再設定
    buf.setBufferSize(10);
    // データ読み込み(エラー)
    buf.readBuffer(1);

    fprintf(stdout, "Normal pattern ------\n"); 
    for (uint i=0; i<2*buf.getBufferSize(); i++)
    {
        Data data_2;
        data_2.number = i;
        // データ書き込み(正常)
        buf.writeBuffer(data_2);
        fprintf(stdout, "%d / %d\n",i, 2*buf.getBufferSize()-1); 
        // 1. データ読み込み(最古のデータ 0 or 10)
        fprintf(stdout, "1. buf.data[%d].number: %d\n", 
            i%buf.getBufferSize(),
            buf.readBuffer(i%buf.getBufferSize()).number);
        // 2. データ読み込み(最新のデータ 0 to 19)
        fprintf(stdout, "2. buf.data[%d].number: %d\n", 
            0,buf.readBuffer(0).number);
    }
    return 0;
}

テンプレートのコンパイル方法

テンプレートのコンパイル方法を記載する。
$ g++ ./ring_buffer.cpp -std=c++11 -o ring_buffer_sample

テンプレートの実行結果

テンプレートの実行結果を記載する。
$ ./ring_buffer_sample
Error pattern -------
Error: [RingBuffer] Please set buffer size more than 0.
Error: [RingBuffer] Buffer is not allocated.
Error: [RingBuffer] Read pointer is out of buffer. (1 / 0)
Error: [RingBuffer] Read pointer is out of buffer. (1 / 0)
Normal pattern ------
0 / 19
1. buf.data[0].number: 0
2. buf.data[0].number: 0
1 / 19
1. buf.data[1].number: 0
2. buf.data[0].number: 1
2 / 19
1. buf.data[2].number: 0
2. buf.data[0].number: 2
3 / 19
1. buf.data[3].number: 0
2. buf.data[0].number: 3
4 / 19
1. buf.data[4].number: 0
2. buf.data[0].number: 4
5 / 19
1. buf.data[5].number: 0
2. buf.data[0].number: 5
6 / 19
1. buf.data[6].number: 0
2. buf.data[0].number: 6
7 / 19
1. buf.data[7].number: 0
2. buf.data[0].number: 7
8 / 19
1. buf.data[8].number: 0
2. buf.data[0].number: 8
9 / 19
1. buf.data[9].number: 0
2. buf.data[0].number: 9
10 / 19
1. buf.data[0].number: 10
2. buf.data[0].number: 10
11 / 19
1. buf.data[1].number: 10
2. buf.data[0].number: 11
12 / 19
1. buf.data[2].number: 10
2. buf.data[0].number: 12
13 / 19
1. buf.data[3].number: 10
2. buf.data[0].number: 13
14 / 19
1. buf.data[4].number: 10
2. buf.data[0].number: 14
15 / 19
1. buf.data[5].number: 10
2. buf.data[0].number: 15
16 / 19
1. buf.data[6].number: 10
2. buf.data[0].number: 16
17 / 19
1. buf.data[7].number: 10
2. buf.data[0].number: 17
18 / 19
1. buf.data[8].number: 10
2. buf.data[0].number: 18
19 / 19
1. buf.data[9].number: 10
2. buf.data[0].number: 19

備考


まとめ


  • C++でリングバッファを記述するテンプレートを記載した

参考文献



変更履歴


  1. 2019/08/28: 新規作成
  2. 2019/08/28: 備考追記
  3. 2019/09/02: 仕様追記

0 件のコメント:

コメントを投稿

MQTTの導入

背景 IoTデバイスの接続環境構築のため、MQTT(mosquitto)の導入を行った。 記事の目的 MQTT(mosquitto)をUbuntuに導入する mosquitto ここではmosquittoについて記載する。 MQTT MQTT(Message Qu...