思うだけで学ばない日記 2.0

思うだけで学ばない日記から移転しました☆!よろしくお願いします。

【行単位で排他】std::ostreamのお手軽カスタマイズ【時刻付き】(2)

ソース

※↓↓↓「仮想メディア」なる語はGMA0BNが勝手に作ったもの
これわ実質的にバッファなんだけども、std::streambufが管理するバッファと紛らわしいので区別を付ける意味で、


<SafeStreamBuffer.h>

#pragma once

#define LOG_DATE_FORMAT     2

class SafeStreamBuffer : public std::streambuf
{
private:
    typedef char _Elem;
    typedef std::char_traits<char> _Traits;
    typedef _Traits::int_type int_type;

private:
    _Elem* m_aElemOut;      // 仮想メディア実体(出力方向)
    int bufSz;
    int wrIdx;
    int rdIdx;
    /*= 出力メインのクラスなので、
     *  入力方向の仮想メディアは持たない。
     */

    std::istream& m_ist;
    std::ostream& m_ost;
    CCritSec& m_syncRoot;

public:
    /// 新しいインスタンスを初期化します。
    SafeStreamBuffer(std::istream& ist, std::ostream& ost, CCritSec& syncRoot);

private:
    /// (禁止)
    SafeStreamBuffer(const SafeStreamBuffer&);

public:
    /// インスタンスを破棄します。
    virtual ~SafeStreamBuffer();

    /// ストリームに1文字書き込まれたとき呼ばれます。
    virtual int_type overflow(int_type ch = _Traits::eof());

    /// ストリームから1文字読み出されるとき呼ばれます。
    virtual int_type underflow();

    /// 仮想メディアから1文字読み出します。
    virtual int_type uflow();

    /// 読み出した文字を仮想メディアに戻します。
    virtual int_type pbackfail(int_type ch = _Traits::eof());

    /// 仮想メディアと物理メディアを同期します。
    virtual int_type sync();

private:
    /// 現在の日時を出力します。(秒まで)
    static std::ostream& PutDateClassic(std::ostream& stream);

    /// 現在の日時を出力します。(msまで)
    static std::ostream& PutDateFine(std::ostream& stream);

};

<SafeStreamBuffer.cpp>

#include "stdafx.h"
#include "SafeStreamBuffer.h"

using namespace std;

const int bufSzStd = 512;
const int bufSzDelta = 512;

/// 新しいインスタンスを初期化します。
SafeStreamBuffer::SafeStreamBuffer(std::istream& ist, std::ostream& ost, CCritSec& syncRoot)
    : wrIdx(0), rdIdx(0), bufSz(bufSzStd),
      m_ist(ist), m_ost(ost), m_syncRoot(syncRoot)
{
    setbuf(0, 0);       // std::streambuf内でバッファリングしない
    m_aElemOut = (_Elem*)malloc((bufSz + 1) * sizeof(_Elem));
}

/// インスタンスを破棄します。
SafeStreamBuffer::~SafeStreamBuffer()
{
    free(m_aElemOut);
}

/// (std::streambuf内でバッファリングしないので)
/// ストリームに1文字書き込まれたとき呼ばれます。
SafeStreamBuffer::int_type SafeStreamBuffer::overflow(int_type ch)
{
    const int_type eof = _Traits::eof();
    if (ch == eof)
        return eof;
    if (wrIdx >= bufSz) {
        int n = bufSz + bufSzDelta;
        _Elem* p = (_Elem*)realloc(m_aElemOut, (n + 1) * sizeof(_Elem));
        if (p == 0)
            return EOF;
        m_aElemOut = p;
        bufSz = n;
    }
    m_aElemOut[wrIdx++] = ch;
    return 0;
}

/// (std::streambuf内でバッファリングしないので)
/// ストリームから1文字読み出されるとき呼ばれます。
SafeStreamBuffer::int_type SafeStreamBuffer::underflow()
{
    return pbackfail(uflow());
}

/// 仮想メディアから1文字読み出します。
SafeStreamBuffer::int_type SafeStreamBuffer::uflow()
{
    const int_type eof = _Traits::eof();
    if (rdIdx >= wrIdx)
        return eof;         // 読むべきデータ無し
    return m_aElemOut[rdIdx++];
    /*= 出力メインのクラスなので、
     *  書き込んだ内容のうち、出力前のものが読めるだけ、
     *  という適当実装。
     */
}

/// 読み出した文字を仮想メディアに戻します。
SafeStreamBuffer::int_type SafeStreamBuffer::pbackfail(int_type ch)
{
    const int_type eof = _Traits::eof();
    if (ch == eof)
        return eof;
    if (rdIdx <= 0)
        return eof;
    --rdIdx;
    if (m_aElemOut[rdIdx] != ch)
        return eof;
    return 0;
}

/// 現在の日時を出力します。(秒まで)
std::ostream& SafeStreamBuffer::PutDateClassic(std::ostream& ost)
{
    time_t timer;
    time(&timer);
    struct tm t_st;
    errno_t err = localtime_s(&t_st, &timer);
    if (err == 0) {
        int year  = t_st.tm_year + 1900;
        int month = t_st.tm_mon + 1;
        int day   = t_st.tm_mday;
        int hour  = t_st.tm_hour;
        int min   = t_st.tm_min;
        int sec   = t_st.tm_mon;

        // 基数とfillCharを変更
        ios::fmtflags sv_f = ost.setf(ios::dec, ios::basefield);
        ost.unsetf(ios::showpos);
        _Elem sv_c = ost.fill('0');

        // 日時出力
        ost << "["
            << setw(4) << year << "-"
            << setw(2) << day  << "-"
            << setw(2) << day  << "T"
            << setw(2) << hour << ":"
            << setw(2) << min  << ":"
            << setw(2) << sec  << "] ";

        // 元に戻す
        ost.fill(sv_c);
        ost.setf(sv_f);
    }

    return ost;
}

/// 現在の日時を出力します。(msまで)
std::ostream& SafeStreamBuffer::PutDateFine(std::ostream& ost)
{
    SYSTEMTIME t_st;
    GetLocalTime(&t_st);
    int year  = t_st.wYear;
    int month = t_st.wMonth;
    int day   = t_st.wDay;
    int hour  = t_st.wHour;
    int min   = t_st.wMinute;
    int sec   = t_st.wSecond;
    int msec  = t_st.wMilliseconds;

    // 基数とfillCharを変更
    ios::fmtflags sv_f = ost.setf(ios::dec, ios::basefield);
    ost.unsetf(ios::showpos);
    _Elem sv_c = ost.fill('0');

    // 日時出力
    ost << "["
        << setw(4) << year  << "-"
        << setw(2) << month << "-"
        << setw(2) << day   << "T"
        << setw(2) << hour  << ":"
        << setw(2) << min   << ":"
        << setw(2) << sec   << "."
        << setw(4) << msec  << "] ";

    // 元に戻す
    ost.fill(sv_c);
    ost.setf(sv_f);

    return ost;
}

/// 仮想メディアと物理メディアを同期します。
SafeStreamBuffer::int_type SafeStreamBuffer::sync()
{
    CAutoLock lock(&m_syncRoot);
    /*= 以降ブロックを抜けるまでがクリティカルセクションになる。
     */

    // 日時を記録
#if LOG_DATE_FORMAT == 1
    PutDateClassic(m_ost);
#elif LOG_DATE_FORMAT == 2
    PutDateFine(m_ost);
#endif

    // 仮想メディア内容を出力
    m_aElemOut[wrIdx] = 0;          // 終端文字('\0')付加
    m_ost << m_aElemOut << flush;

    // 仮想メディアを空にする
    rdIdx = wrIdx = 0;

    return (m_ost.fail()) ? _Traits::eof() : 0;
    /*= 出力メインのクラスなので
     *  入力方向の同期(物理メディア→仮想メディア)は省略。
     */
}

今回は目的と時間との兼ね合いによりchar文字列にしか対応していないが、もちろんこれはbasic_ostreamやbasic_istreamといったテンプレートを使えばワイド文字対応も可能*1

参考資料

iostreamのしくみ:[ttp
//www.kab-studio.biz/Programing/Codian/iostream/06.html]:これ最強マジお勧め。
今回のSafeStreamBufferの実装は、キャラクタ型デバイスへの対応に当たる(のだと思う)。もちろん天下のSTL様はブロック型デバイスにも対応しており、両方のサンプルが記載されている(と言っても過言ではない希ガス)。
std::ostreamをカスタマイズしてロギングさせる例:[ttp
//mattn.kaoriya.net/software/lang/c/20110402011210.htm]:時刻を付けるアイデアだけいただいた。掲載ソースに行単位の排他は無い。また、std::ostringstreamを使ってるのは冗長に思える。推測だが時刻表示の都合で変えたストリームの状態を元に戻す方法をご存知無かったのかしらむ*2

*1:basic_stringのワイド文字対応とかと同じ要領

*2:いやパッと見の印象なので、何か深い理由があるのかも試練が、