web-dev-qa-db-ja.com

C ++でカスタム入力ストリームを作成する方法

私は現在C++(Javaから)を学んでおり、C++でIOストリームを適切に使用する方法を理解しようとしています。

画像のピクセルを含むImageクラスがあり、抽出演算子をオーバーロードしてストリームから画像を読み取るとします。

_istream& operator>>(istream& stream, Image& image)
{
    // Read the image data from the stream into the image
    return stream;
}
_

だから今、私はこのような画像を読むことができます:

_Image image;
ifstream file("somepic.img");
file >> image;
_

しかし今、同じ抽出演算子を使用して、カスタムストリームから画像データを読み取りたいと思います。圧縮形式の画像を含むファイルがあるとしましょう。したがって、ifstreamを使用する代わりに、独自の入力ストリームを実装することもできます。少なくともそれは私がJavaでそれを行う方法です。 Javaでは、InputStreamクラスを拡張し、int read()メソッドを実装するカスタムクラスを記述します。これは非常に簡単です。使用方法は次のようになります。

_InputStream stream = new CompressedInputStream(new FileInputStream("somepic.imgz"));
image.read(stream);
_

したがって、同じパターンを使用して、C++でこれを実行したい場合があります。

_Image image;
ifstream file("somepic.imgz");
compressed_stream stream(file);
stream >> image;
_

しかし、それは間違った方法かもしれません、わかりません。 istreamクラスの拡張はかなり複雑に見えますが、いくつかの検索の後、代わりにstreambufの拡張に関するヒントを見つけました。しかし、これは はそのような単純なタスクのために非常に複雑に見えます。

それでは、C++でカスタム入出力ストリーム(またはstreambufs?)を実装する最良の方法は何でしょうか?

溶液

入出力ストリームをまったく使用せず、イテレーター、ブースト、またはカスタムIOインターフェイスを代わりに使用することを提案しました。これらは有効な代替手段かもしれませんが、私の質問はiostreamsです。読みやすくするために、ヘッダーとコードの分離はなく、std名前空間全体がインポートされます(これは実際のコードでは悪いことです)。

この例は、垂直xorエンコードされた画像の読み取りと書き込みに関するものです。フォーマットはとても簡単です。各バイトは2ピクセル(ピクセルあたり4ビット)を表します。各行は前の行とxorされます。この種のエンコーディングは、圧縮のために画像を準備します(通常、圧縮しやすい0バイトが多くなります)。

_#include <cstring>
#include <fstream>

using namespace std;

/*** vxor_streambuf class ******************************************/

class vxor_streambuf: public streambuf
{
public:
    vxor_streambuf(streambuf *buffer, const int width) :
        buffer(buffer),
        size(width / 2)
    {
        previous_line = new char[size];
        memset(previous_line, 0, size);
        current_line = new char[size];
        setg(0, 0, 0);
        setp(current_line, current_line + size);
    }

    virtual ~vxor_streambuf()
    {
        sync();
        delete[] previous_line;
        delete[] current_line;
    }

    virtual streambuf::int_type underflow()
    {
        // Read line from original buffer
        streamsize read = buffer->sgetn(current_line, size);
        if (!read) return traits_type::eof();

        // Do vertical XOR decoding
        for (int i = 0; i < size; i += 1)
        {
            current_line[i] ^= previous_line[i];
            previous_line[i] = current_line[i];
        }

        setg(current_line, current_line, current_line + read);
        return traits_type::to_int_type(*gptr());
    }

    virtual streambuf::int_type overflow(streambuf::int_type value)
    {
        int write = pptr() - pbase();
        if (write)
        {
            // Do vertical XOR encoding
            for (int i = 0; i < size; i += 1)
            {
                char tmp = current_line[i];
                current_line[i] ^= previous_line[i];
                previous_line[i] = tmp;
            }

            // Write line to original buffer
            streamsize written = buffer->sputn(current_line, write);
            if (written != write) return traits_type::eof();
        }

        setp(current_line, current_line + size);
        if (!traits_type::eq_int_type(value, traits_type::eof())) sputc(value);
        return traits_type::not_eof(value);
    };

    virtual int sync()
    {
        streambuf::int_type result = this->overflow(traits_type::eof());
        buffer->pubsync();
        return traits_type::eq_int_type(result, traits_type::eof()) ? -1 : 0;
    }

private:
    streambuf *buffer;
    int size;
    char *previous_line;
    char *current_line;
};


/*** vxor_istream class ********************************************/

class vxor_istream: public istream
{
public:
    vxor_istream(istream &stream, const int width) :
        istream(new vxor_streambuf(stream.rdbuf(), width)) {}

    virtual ~vxor_istream()
    {
        delete rdbuf();
    }
};


/*** vxor_ostream class ********************************************/

class vxor_ostream: public ostream
{
public:
    vxor_ostream(ostream &stream, const int width) :
        ostream(new vxor_streambuf(stream.rdbuf(), width)) {}

    virtual ~vxor_ostream()
    {
        delete rdbuf();
    }
};


/*** Test main method **********************************************/

int main()
{
    // Read data
    ifstream infile("test.img");
    vxor_istream in(infile, 288);
    char data[144 * 128];
    in.read(data, 144 * 128);
    infile.close();

    // Write data
    ofstream outfile("test2.img");
    vxor_ostream out(outfile, 288);
    out.write(data, 144 * 128);
    out.flush();
    outfile.close();

    return 0;
}
_
51
kayahr

C++で新しいストリームを作成する適切な方法は、std::streambufから派生し、読み取り用のunderflow()操作と、書き込み用のoverflow()およびsync()操作をオーバーライドすることです。 。目的のために、別のストリームバッファー(およびrdbuf()を使用してストリームバッファーを抽出できるストリーム)を引数として受け取り、このストリームバッファーに関して独自の操作を実装するフィルターストリームバッファーを作成します。 。

ストリームバッファの基本的な概要は次のようになります。

class compressbuf
    : public std::streambuf {
    std::streambuf* sbuf_;
    char*           buffer_;
    // context for the compression
public:
    compressbuf(std::streambuf* sbuf)
        : sbuf_(sbuf), buffer_(new char[1024]) {
        // initialize compression context
    }
    ~compressbuf() { delete[] this->buffer_; }
    int underflow() {
        if (this->gptr() == this->egptr()) {
            // decompress data into buffer_, obtaining its own input from
            // this->sbuf_; if necessary resize buffer
            // the next statement assumes "size" characters were produced (if
            // no more characters are available, size == 0.
            this->setg(this->buffer_, this->buffer_, this->buffer_ + size);
        }
        return this->gptr() == this->egptr()
             ? std::char_traits<char>::eof()
             : std::char_traits<char>::to_int_type(*this->gptr());
    }
};

underflow()の表示方法は、使用されている圧縮ライブラリによって異なります。私が使用したほとんどのライブラリは、満たされる必要があり、まだ消費されていないバイトを保持する内部バッファを保持しています。通常、解凍をunderflow()にフックするのはかなり簡単です。

ストリームバッファが作成されたら、ストリームバッファでstd::istreamオブジェクトを初期化するだけです。

std::ifstream fin("some.file");
compressbuf   sbuf(fin.rdbuf());
std::istream  in(&sbuf);

ストリームバッファを頻繁に使用する場合、オブジェクト構築をicompressstreamなどのクラスにカプセル化することができます。基本クラスstd::iosは仮想ベースであり、ストリームバッファーが格納される実際の場所であるため、これを行うには少し注意が必要です。ポインターをstd::iosに渡す前にストリームバッファーを構築するには、いくつかのフープをジャンプする必要があります。virtual基本クラスを使用する必要があります。これは大まかにどのように見えるかです:

struct compressstream_base {
    compressbuf sbuf_;
    compressstream_base(std::streambuf* sbuf): sbuf_(sbuf) {}
};
class icompressstream
    : virtual compressstream_base
    , public std::istream {
public:
    icompressstream(std::streambuf* sbuf)
        : compressstream_base(sbuf)
        , std::ios(&this->sbuf_)
        , std::istream(&this->sbuf_) {
    }
};

(合理的に正しいことをテストする簡単な方法なしでこのコードを入力しただけです。タイプミスを期待してくださいが、全体的なアプローチは説明のように機能するはずです)

58
Dietmar Kühl

ブースト(C++に真剣に取り組んでいる場合は既にあるはずです)には、IOストリーム: boost.iostreams の拡張とカスタマイズ専用のライブラリー全体があります。

特に、いくつかの一般的な形式( bzip2gzlib 、および zlib )の圧縮解除ストリームが既にあります

ご覧のように、streambufの拡張は複雑な作業かもしれませんが、ライブラリを使用すると、必要に応じて 独自のフィルタリングstreambufを書く を簡単に実行できます。

9
Cubbi

あなたが恐ろしいデザインの恐ろしい死を死にたくない限り、しないでください。 IOstreamは、標準ライブラリの最悪のコンポーネントであり、ロケールよりもさらに悪いコンポーネントです。イテレータモデルははるかに便利であり、istream_iteratorを使用してストリームからイテレータに変換できます。

4
Puppy

@DeadMGに同意しますが、iostreamの使用はお勧めしません。貧弱な設計は別として、パフォーマンスはしばしば、古いCスタイルのI/Oのパフォーマンスよりも劣ります。特定のI/Oライブラリに固執することはありませんが、代わりに、必要なすべての操作を含むインターフェイス(抽象クラ​​ス)を作成します。次に例を示します。

class Input {
 public:
  virtual void read(char *buffer, size_t size) = 0;
  // ...
};

次に、このインターフェイスをC I/O、iostream、mmapなどに実装できます。

1
vitaut

おそらくこれを行うことは可能ですが、C++でのこの機能の「正しい」使用法ではないと感じています。 iostream >>および<<演算子は、イメージの解析と読み込みではなく、_class Person_の「名前、通り、町、郵便番号」の作成など、かなり単純な操作を対象としています。 stream :: read()-Image(astream);を使用することではるかによくできます。Dietmarで説明されているように、圧縮用のストリームを実装できます。

0
Mats Petersson