69

私は現在 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++ でカスタム入出力ストリーム (またはストリームバッファ?) を実装する最良の方法は何ですか?

解決

一部の人々は、iostream をまったく使用せず、代わりに反復子、ブースト、またはカスタム IO インターフェイスを使用することを提案しました。これらは有効な代替手段かもしれませんが、私の質問は iostream に関するものでした。受け入れられた回答により、以下のコード例が得られました。読みやすくするために、ヘッダーとコードの分離はなく、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;
}
4

5 に答える 5

68

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);

ストリームバッファを頻繁に使用する場合は、オブジェクト構造をクラスにカプセル化することをお勧めしますicompressstreamstd::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_) {
    }
};

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

于 2012-12-29T22:24:17.193 に答える
8

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

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

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

于 2012-12-29T22:50:00.340 に答える
4

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

于 2012-12-29T22:23:47.763 に答える
1

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

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

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

于 2012-12-30T14:34:48.207 に答える
0

これはおそらく可能ですが、C++でのこの機能の「正しい」使用法ではないと思います。class Personiostream >>および<<演算子は、画像の解析や読み込みではなく、の「名前、通り、町、郵便番号」の記述など、かなり単純な操作を目的としています。これは、stream :: read()を使用して行う方がはるかに優れていImage(astream);ます。Dietmarで説明されているように、圧縮用のストリームを実装できます。

于 2012-12-29T22:28:29.973 に答える