prefixed_ostream

目的

次のような出力を行いたい.


#include <iostream>
#include <fstream>
#include "prefixed_ostream.h"

int main(void)
{
    prefixed_ostream  warning(std::cerr, "WARNING: ");  // 警告出力用フィルタ
    prefixed_ostream  error(std::cerr, "ERROR: ");      // エラー出力用フィルタ

    warning << "This is a warning message." << std::endl;
    error << "This is a long error message from here...\n"
          << "(snip)\n"
          << "to here." << std::endl;

    std::ofstream     ofs("output.txt");    // 例外処理は省略
    prefixed_ostream  comment(ofs, "# ");   // コメント出力用フィルタ

    ofs << "This is a usual line." << std::endl;
    comment << "This is a comment line." << std::endl;
    ofs << "This is a usual line again." << std::endl;

    return 0;
}

$ ./a.out
WARNING: This is a warning message.
ERROR: This is a long error message from here...
ERROR: (snip)
ERROR: to here.
$ cat output.txt
This is a usual line.
# This is a comment line.
This is a usual line again.
$ 

つまり,指定した文字列を各行の先頭に自動的に付記して出力できるようにしたい.

実現方法

このページで提案されている 「フィルタ」 なる概念を利用する.

実装例

バッファが溢れた場合の処理が不適切なためにセグフォが発生し得る事が判明したので,関数 write_with_prefix() の処理を修正 (及びメンバ is_line_head_ を追加).

極力 1 行単位で出力するように修正.


#if !defined PREFIXED_OSTREAM_H_INCLUDED_
#define PREFIXED_OSTREAM_H_INCLUDED_

#include <streambuf>

/// プレフィックスを付記する出力ストリームクラステンプレート
template <typename CharT, typename Traits = std::char_traits<CharT> >
class basic_prefixed_ostream : public std::basic_ostream<CharT, Traits>
{
public:
    /**
     * @brief コンストラクタ
     * @param [in,out] os          対象とする出力ストリーム
     * @param [in]     prefix      プレフィックス
     * @param [in]     buffer_size バッファのサイズ
     */
    basic_prefixed_ostream(
        std::ostream& os,
        const char* prefix = "# ", std::size_t buffer_size = 1024)
        : std::basic_ostream<CharT, Traits>(
            new streambuf<CharT, Traits>(os, prefix, buffer_size)) {}

    /// デストラクタ
    virtual ~basic_prefixed_ostream()
    {
        this->flush();
        delete this->rdbuf();
    }

protected:
    /// basic_prefixed_ostream 専用のストリームバッファクラステンプレート
    template <class CharT2, class Traits2 = std::char_traits<CharT2> >
    class streambuf : public std::basic_streambuf<CharT2, Traits2>
    {
        typedef CharT2                                      char_type;
        typedef Traits2                                     traits_type;
        typedef typename traits_type::int_type              int_type;
        typedef std::basic_ostream<char_type, traits_type>  ostream_type;

        ostream_type*     os_;           ///< 対象とする出力ストリーム
        const char_type*  prefix_;       ///< プレフィックス
        std::size_t       prefix_size_;  ///< prefix_ の文字列長
        char_type*        buffer_;       ///< バッファ
        std::size_t       buffer_size_;  ///< バッファのサイズ
        char_type*        head_;         ///< 書き出すデータの先頭位置
        bool              is_line_head_; ///< 真の時 prefix_ を出力する

        /// バッファをクリアする
        inline void clear_buffer(void)
        {
            traits_type::assign(buffer_, 0, buffer_size_);
            this->setp(buffer_, buffer_ + buffer_size_);
            head_ = buffer_;
        }

        /**
         * @brief プレフィックスを付記して出力する
         * @param [in] force 真の時バッファの中身を強制的に出力する
         */
        void write_with_prefix(bool force)
        {
            // 改行文字が現れる度に 1 行ずつ出力する
            for (char_type* pos = head_; pos < this->pptr(); pos++)
                if (*pos == '\n')
                {
                    if (is_line_head_)
                        os_->write(prefix_, prefix_size_);
                    os_->write(head_, pos + 1 - head_);
                    head_ = pos + 1;
                    is_line_head_ = true;
                }

            // 改行文字が現れないままバッファの先頭から末尾まで使い切った場合
            // または force が真の時 バッファの中身を強制的に全て出力する
            // force が偽であり かつバッファの先頭の方に余裕がある場合は
            // 詰めるだけにする
            if (head_ != this->pptr() && this->pptr() == this->epptr())
            {
                const std::size_t   length = this->pptr() - head_;
                if (head_ == this->pbase() || force)
                {
                    if (is_line_head_)
                        os_->write(prefix_, prefix_size_);
                    os_->write(head_, length);
                    this->clear_buffer();
                    is_line_head_ = false;
                }
                else
                {
                    char_type   temp[length];
                    traits_type::copy(temp, head_, length);
                    this->clear_buffer();
                    traits_type::copy(head_, temp, length);
                    this->pbump(length);
                }
            }
        }

    public:
        /**
         * @brief コンストラクタ
         * @param [in] os          対象とする出力ストリーム
         * @param [in] prefix      プレフィックス
         * @param [in] buffer_size バッファのサイズ
         */
        streambuf(
            ostream_type& os, const char* prefix, std::size_t buffer_size)
            : os_(&os), prefix_(prefix),
              buffer_(new char_type[buffer_size]),
              buffer_size_(buffer_size), is_line_head_(true)
        {
            prefix_size_ = traits_type::length(prefix_);
            this->clear_buffer();
        }

        /// デストラクタ
        virtual ~streambuf()
        {
            delete [] buffer_;
        }

    protected:
        /// バッファが溢れた時に呼ばれるメンバ関数
        virtual int_type overflow(int_type c = traits_type::eof())
        {
            this->write_with_prefix(false);

            if (c != traits_type::eof())
            {
                *(this->pptr()) = traits_type::to_char_type(c);
                this->pbump(1);
                return traits_type::not_eof(c);
            }
            else
                return traits_type::eof();
        }

        /// 書き出し先とバッファを同期する時などに呼ばれるメンバ関数
        virtual int sync(void)
        {
            this->write_with_prefix(true);
            return 0;
        }
    };
};

// 頻繁に利用されるであろうクラスを予め型定義しておく.(<iosfwd> 参照)
typedef basic_prefixed_ostream<char>  prefixed_ostream;

#endif  // !define PREFIXED_OSTREAM_H_INCLUDED_

備考

以下のコンパイラでのみ動作確認済み.

  • g++ (GCC) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.0)
  • g++ (GCC) 4.1.2 (Gentoo 4.1.2)

余談

std::basic_streambuf を継承するなんて真似,自力で見出すことは到底不可能だと思うんだけど.