如何在C ++中编写自定义输入流 [英] How to write custom input stream in C++

查看:155
本文介绍了如何在C ++中编写自定义输入流的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在学习C ++(来自Java),我试图理解如何在C ++中正确使用IO流。

I'm currently learning C++ (Coming from Java) and I'm trying to understand how to use IO streams properly in C++.

假设我有一个 Image 类,它包含图像的像素,并且重载了提取操作符来自流的图像:

Let's say I have an Image class which contains the pixels of an image and I overloaded the extraction operator to read the image from a stream:

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

现在我可以读取这样的图片:

So now I'm able to read an image like this:

Image image;
ifstream file("somepic.img");
file >> image;

但现在我想使用相同的提取操作符从自定义流读取图像数据。假设我有一个包含压缩形式的图像的文件。所以,而不是使用ifstream我可能想实现自己的输入流。至少这是我将在Java做它。在Java中,我将编写一个扩展 InputStream 类并实现 int read()方法的自定义类。这很容易。使用方法如下:

But now I want to use the same extraction operator to read the image data from a custom stream. Let's say I have a file which contains the image in compressed form. So instead of using ifstream I might want to implement my own input stream. At least that's how I would do it in Java. In Java I would write a custom class extending the InputStream class and implementing the int read() method. So that's pretty easy. And usage would look like this:

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

所以使用相同的模式也许我想在C ++中这样做:

So using the same pattern maybe I want to do this in C++:

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

但也许这是错误的方式,不知道。扩展 istream 类看起来很复杂,经过一些搜索,我发现了一些关于扩展 streambuf 的提示。但此示例看起来非常复杂对于这样一个简单的任务。

But maybe that's the wrong way, don't know. Extending the istream class looks pretty complicated and after some searching I found some hints about extending streambuf instead. But this example looks terribly complicated for such a simple task.

那么在C ++中实现自定义输入/输出流(或streambuf?)的最好方法是什么?

So what's the best way to implement custom input/output streams (or streambufs?) in C++?

有些人建议不要使用iostreams,而是使用迭代器,boost或自定义IO接口。这些可能是有效的替代品,但我的问题是关于iostreams。接受的答案导致了下面的示例代码。为了更容易阅读,没有标题/代码分隔,整个std命名空间被导入(我知道这是一个坏东西在实际代码)。

Some people suggested not using iostreams at all and to use iterators, boost or a custom IO interface instead. These may be valid alternatives but my question was about iostreams. The accepted answer resulted in the example code below. For easier reading there is no header/code separation and the whole std namespace is imported (I know that this is a bad thing in real code).

这个例子是关于读取和写入垂直 - 经编码的图像。格式很容易。每个字节表示两个像素(每像素4位)。每行与上一行进行xor'd。这种编码准备压缩的图像(通常导致大量的0字节,这更容易压缩)。

This example is about reading and writing vertical-xor-encoded images. The format is pretty easy. Each byte represents two pixels (4 bits per pixel). Each line is xor'd with the previous line. This kind of encoding prepares the image for compression (usually results in lot of 0-bytes which are easier to compress).

#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;
}


推荐答案

C ++中的新流是从 std :: streambuf 派生并覆盖 underflow()操作读取和 overflow() sync()写操作。为了你的目的,你可以创建一个过滤流缓冲区,它接收另一个流缓冲区(可能还有一个流,可以使用 rdbuf()作为参数提取流缓冲区)

The proper way to create a new stream in C++ is to derive from std::streambuf and to override the underflow() operation for reading and the overflow() and sync() operations for writing. For your purpose you'd create a filtering stream buffer which takes another stream buffer (and possibly a stream from which the stream buffer can be extracted using rdbuf()) as argument and implements its own operations in terms of this stream buffer.

流缓冲区的基本轮廓将是这样:

The basic outline of a stream buffer would be something like this:

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 )

How underflow() looks exactly depends on the compression library being used. Most libraries I have used keep an internal buffer which needs to be filled and which retains the bytes which are not yet consumed. Typically, it is fairly easy to hook the decompression into underflow().

创建流缓冲区后,只需初始化 std :: istream object with the stream buffer:

Once the stream buffer is created, you can just initialize an std::istream object with the stream buffer:

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

如果要经常使用流缓冲区,可能需要将对象构造封装到类,例如 icompressstream 。这样做有点棘手,因为基类 std :: ios 是一个虚拟基础,是流缓冲区存储的实际位置。要在传递指向 std :: ios 的指针之前构造流缓冲区,因此需要跳过几个圈:它需要使用 / code>基类。下面是大致的情况:

If you are going to use the stream buffer frequently, you might want to encapsulate the object construction into a class, e.g., icompressstream. Doing so is a bit tricky because the base class std::ios is a virtual base and is the actual location where the stream buffer is stored. To construct the stream buffer before passing a pointer to a std::ios thus requires jumping through a few hoops: It requires the use of a virtual base class. Here is how this could look roughly:

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_) {
    }
};

(我只是输入这个代码没有一个简单的方法来测试它是相当正确;但总体方法应该按照描述工作)

(I just typed this code without a simple way to test that it is reasonably correct; please expect typos but the overall approach should work as described)

这篇关于如何在C ++中编写自定义输入流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆