RXマイコン(組み込み向け)formatクラスの改修

前回、「fixed_string」テンプレートを紹介したが、本題の「format」に移る。

「format」クラスは、「boost::format」の組み込みマイコン向けに仕様を変更した俺俺クラスだ。
それでも、sprintf などの他ライブラリに依存していない為、「format.hpp」のみで簡潔するようになっている。
※インクルード部

#include <type_traits>
#include <unistd.h>

最終的な出力は、「iostream」では無く、ターミナルへの文字出力で、「syscalls」の「write」を使っている。(ディスクリプタは stdout)

また、出力クラスは、テンプレートの引数で与えており、出力ファンクタを実装する事で、色々な出力形態に対応できる。
以前は、固定文字列クラスを実装していなかった為、簡易固定文字列を作り、利用していたが、柔軟性が無く拡張しずらかった為文字列クラスを使える仕様に修正した。
※出来てみると、よりシンプルな構造になり、使いやすく、判りやすくなった。
クラスの型は、こんな感じだ。

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  簡易 format クラス
    @param[in]  CHAOUT  文字出力ファンクタ
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <class CHAOUT>
class basic_format {

※「CHAOUT」へは、基本1文字づつ追加する。

標準の文字出力クラスは以下のようになっている(stdout)

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  標準出力ファンクタ
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
class stdout_chaout {

    uint32_t     size_;

public:
    //-----------------------------------------------------------------//
    /*!
        @brief  コンストラクター
    */
    //-----------------------------------------------------------------//
    stdout_chaout() : size_(0) { } 

    void operator() (char ch) {
        char tmp = ch;
        write(1, &tmp, 1);  // FD by stdout
        ++size_;
    }

    void clear() { size_ = 0; };

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

「format」クラスの一番大きな変更は、「CHAOUT」をスタティックとした点で。

    static CHAOUT  chaout_;
・・・
};
// テンプレートクラス定義の外で、static の実態を宣言する。
template <class CHAOUT> CHAOUT basic_format<CHAOUT>::chaout_;

文字出力が、都度、リセットされないので、追加などの制御がやりやすくなった。
CHAOUT の暗黙的な仕様として、「operator(char)」、「size()」があれば良い。
※「clear()」は利便性の為用意してある。

標準的な文字列クラスを使った場合は以下のように、文字の終端を制御するクラスも与える事で、内部的制御を一般化できる。

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  文字列クラス、出力ファンクタ
    @param[in]  STR     文字列クラス
    @param[in]  TERM    ターミネーター・ファンクタ
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <class STR, class TERM>
class string_chaout {

・・・

    void operator () (char ch) {
        str_ += ch;
        if(str_.size() >= str_.capacity()) {
            term_(str_.c_str(), str_.size());
            str_.clear();
        }           
    }

・・・

};

「TERM」クラスは、「STR」クラスへの文字追加が出来なくなる前に、挙動を制御する。

    void flush() {
        if(str_.size() > 0) {
            term_(str_.c_str(), str_.size());
            str_.clear();
        }
    }

    STR& at_str() { return str_; }

    TERM& at_term() { return term_; }

「flush()」関数と、「STR」クラスへの参照、「TERM」クラスへの参照を追加してある。
※「format」クラスは、これらの関数を必要としない。

CHAOUT クラスをスタティックにすると、一時的に確保した領域(スタック)などで使いたい場合に、使いにくくなってしまう。
そこで、バッファのポインターとサイズを与えて処理を行えるように、コンストラクターを追加した。(これは以前に作ったものを部分的に作り直した。)
リソースはスタティックなので、利便性を考えて関数をリセットする為の仕組みも追加した。

//-----------------------------------------------------------------//
/*!
    @brief  コンストラクター
    @param[in]  form    フォーマット式
    @param[in]  buff    文字バッファ
    @param[in]  size    文字バッファサイズ
    @param[in]  append  文字バッファに追加する場合「true」
*/
//-----------------------------------------------------------------//
basic_format(const char* form, char* buff, uint32_t size,  bool append = false) noexcept :

また、この場合に合わせて、「memory_chaout」クラスを用意した。

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  メモリー文字列クラス
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
class memory_chaout {
    char*       dst_;
    uint32_t    size_;
    uint32_t    pos_;
public:
    //-----------------------------------------------------------------//
    /*!
        @brief  コンストラクター
        @param[in]  dst     出力先
        @param[in]  size    最大サイズ
    */
    //-----------------------------------------------------------------//
    memory_chaout(char* dst = nullptr, uint32_t size = 0) : dst_(dst), size_(size), pos_(0) { }

    void set(char* dst, uint32_t size)
    {
        if(dst_ != dst || size_ != size) {  // ポインター、サイズ、どちらか異なる場合は常にリセット
            pos_ = 0;
        }
        dst_ = dst;
        size_ = size;
    }

    void operator () (char ch) {
        if(pos_ < (size_ - 1)) {
            dst_[pos_] = ch;
            ++pos_;
            dst_[pos_] = 0;
        }
    }

    void clear() { pos_ = 0; }

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

これで、以下のように使える。

typedef utils::basic_format<utils::memory_chaout> sformat;
    char tmp[256];
    sformat(xxxxxxx, tmp, sizeof(tmp));
    sformat(xxxxxxx, tmp, sizeof(tmp), true);  // 追加する場合

format クラス、完全なソースコード

RXマイコン(組み込み向け)固定文字列クラス

RX マイコン向け http サーバーを実装する過程で、マイクロソフトのIEだ
け極めて遅い応答しかできない事が判った。

Firefox では、1秒以下で表示できるページなのに、IE(最新版)だと、
30秒、またはそれ以上の時間がかかる。

調べてみると、IEのパケット受け取りに、大きな制限があり、異常にインタ
ーバルが大きくなっているようだった、小さいパケットで、分割して送ると、
IEで許容しているサイズに満たない場合、タイムアウトするまで、html の
デコードが遅延され、結果次のパケットを受け付けない。
※何という腐った仕様だろうか・・・
※他の要因として、html のヘッダーにある「Content-Length」が無い、又は
正しくない場合に起こる挙動なのかもしれない。

当初、RXマイコン側での html 文の生成は、行単位で動的に行い、内部の行
生成のタイミングでパケットを送信していた。
※全体の長さは、最終文が来るまで判らないので、「Content-Length」は作成
していないかった。
この方法はメモリーの利用は最低限になるのだが、IEのように大きなパケット
を前提にした、腐ったブラウザでは表示速度に問題が起こる。

そこで、html 文を一旦メモリーに展開して貯めて、一気に転送する方法に変更
する事にした。
※完全に固定されたページでは無く、動的に生成している為
ここで問題なのは、「utils::format」クラスの挙動で、基本、行単位の動作を
前提にしている為、かなり大きく、柔軟に変更する必要がある事がわかり、仕様
を検討した。

そこで、固定サイズの配列で動作する文字列クラスを実装した。
メモリーが潤沢に無い場合には、「std::string」は事実上利用出来ないからで、
メモリーアロケーターが密接に関連したクラスは使う事が出来ない。
※今まで何故作らなかったのか不思議・・・
とりあえず、最低限の機能のみ実装してあり、必要になった機能を追加する。
※当然、std::string の仕様に準拠する。

以下に、ソースの一部を示す。

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
    @brief  固定サイズ文字列クラス
    @param[in]  SIZE    文字列サイズ
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <uint32_t SIZE>
class fixed_string {
    char        text_[SIZE];
    uint32_t    pos_;

public:
    //-----------------------------------------------------------------//
    /*!
        @brief  コンストラクタ
        @param[in]    str     初期設定文字列
    */
    //-----------------------------------------------------------------//
    fixed_string(const char* str = nullptr) : pos_(0) {
        if(str != nullptr) {
            std::strcpy(text_, str);
            pos_ = std::strlen(text_);
        } else {
            text_[pos_] = 0;
        }
    }

    //-----------------------------------------------------------------//
    /*!
        @brief  格納可能な最大サイズを返す(終端の数を除外)
        @return 格納可能な最大サイズ
    */
    //-----------------------------------------------------------------//
    uint32_t capacity() const noexcept { return SIZE - 1; }
...

完全なソースコード