C 言語よりお得な C++ その5

C 言語プログラマーが、C++ に移行して、最初に味わう挫折感は、テンプレートでは無いでしょうか(継承とか、まぁ色々あると思いますが・・)?
大抵、複雑なテンプレートに出会うと、理解不能で、これがどんなふうに機能するのか、理解出来ない事が多く、それが、C++ 不審に陥るキッカケになるかもしれません。
逆説的に、プログラマーにハードルを与える機構を持った優れた言語と言う事も出来るかもしれません。

テンプレートは変態的 define とあんまし変らないと思う人もいるかもしれません、ですが、単なるマクロとは大きく違い、C++ 言語としての仕様をちゃんと網羅しており、より複雑で精妙な事を実現できますし、エラー検査が厳密に行われます。

最初は、簡単な物から、より複雑で、高機能な物へと順番に進んでいけば、良いと思えます。
※ゆっくり歩こうが速くあるこうが、諦めなければ最終目的地に到着します。

さて、テンプレートの題材(ネタ)を考えていたのですが、最近小規模な RX マイコンのプログラムを書いていて良いネタがありましたので紹介します。
※小規模な RX マイコンでは、リソースの関係で、STL がほぼ使えません。

シリアルコミュニケーションのドライバーで使う FIFO を簡単なテンプレートで改修します。
※シリアルコミュニケーションでは、FIFO を使って割り込みルーチンとメイン側でデータの受け渡しを行います。

最初は、以下のような物でした・・

#include <cstdint>
#include <cstdlib>

namespace utils {

    class fifo {

        volatile uint32_t       get_;
        volatile uint32_t       put_;

        char*       buff_;
        uint32_t    size_;

    public:
        fifo() : get_(0), put_(0), buff_(0), size_(0) { }

        ~fifo() { free(buff_); }

        void initialize(uint32_t size) {
            buff_ = static_cast<char*>(malloc(size));
            size_ = size;
            clear();
        }

        void clear() { get_ = put_ = 0; }

        void put(uint8_t v) {
            buff_[put_] = v;
            ++put_;
            if(put_ >= size_) {
                put_ = 0;
            }
        }

        uint8_t get() {
            uint8_t data = buff_[get_];
            ++get_;
            if(get_ >= size_) {
                get_ = 0;
            }
            return data;
        }

        uint32_t length() const {
            if(put_ >= get_) return (put_ - get_);
            else return (size_ + put_ - get_);
        }

        uint32_t pos_get() const { return get_; }

        uint32_t pos_put() const { return put_; }

        uint32_t size() const { return size_; }
    };
}

※記憶割り当てでは、通常 new delete を使うのですが、RX の gcc で、new、delete を呼び出す事で ROM サイズがほんのり大きくなる為、malloc、free にしてあります。

上のクラスは設計が不十分です、動的にバッファのサイズを変えようと思っても、割り当てを廃棄する関数が無い為、実際には変えられません (メモリーリークします)、それに、「initialize」を呼ばずに使うとクラッシュしてしまいます、まぁこの辺は、メンバー関数を追加するとか、ポインターのチェックを行うなどすれば回避できるのですが、だんだん複雑になっていきます。
そもそも、動的に変えられる仕様は必要でしょうか?
通常、シリアルコミュニケーションを使う前に1度確保したら、プログラムが終了するまで、サイズを変える事は稀です。
そこで、これをテンプレート化します。

#include <cstdint>

namespace utils {

    template <uint32_t size_ = 256>
    class fifo {

        volatile uint32_t       get_;
        volatile uint32_t       put_;

        char       buff_[size_];

    public:
        fifo() : get_(0), put_(0) { }

        void clear() { get_ = put_ = 0; }

        void put(uint8_t v) {
            buff_[put_] = v;
            ++put_;
            if(put_ >= size_) {
                put_ = 0;
            }
        }

        uint8_t get() {
            uint8_t data = buff_[get_];
            ++get_;
            if(get_ >= size_) {
                get_ = 0;
            }
            return data;
        }

        uint32_t length() const {
            if(put_ >= get_) return (put_ - get_);
            else return (size_ + put_ - get_);
        }

        uint32_t pos_get() const { return get_; }

        uint32_t pos_put() const { return put_; }

        uint32_t size() const { return size_; }
    };
}

どうでしょうか?、随分シンプルになりました。

インスタンスは、コンパイル時に決定する為、メモリー確保に関連するトラブルからも避けられ、サイズを与えれる(コンパイル時のインスタンス化)ので十分な実用性もあります。

何も指定しないと、256 バイトでサイズが作られますが、以下のようにすれば、サイズを指定できます。

    utils::fifo<512>    fifo_;

このテンプレートは「サイズ」をキーにしている為、サイズが異なれば、違う物とみなされる事になります。
このクラスのポインターを取得したければ、以下のようにサイズが合っていなければなりません。

    utils::fifo<512>*   fifo_ptr_;

    fifo_ptr_ = &fifo_;