如何用C++编写自定义输入流

kay*_*ahr 53 c++ iostream

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

假设我有一个Image包含图像像素的类,我重载了提取操作符以从流中读取图像:

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

所以现在我能够读到这样的图像:

Image image;
ifstream file("somepic.img");
file >> image;
Run Code Online (Sandbox Code Playgroud)

但现在我想使用相同的提取运算符从自定义流中读取图像数据.假设我有一个包含压缩形式图像的文件.因此,我可能想要实现自己的输入流,而不是使用ifstream.至少我就是这样用Java做的.在Java中,我会编写一个扩展InputStream类并实现该int read()方法的自定义类.所以这很容易.用法看起来像这样:

InputStream stream = new CompressedInputStream(new FileInputStream("somepic.imgz"));
image.read(stream);
Run Code Online (Sandbox Code Playgroud)

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

Image image;
ifstream file("somepic.imgz");
compressed_stream stream(file);
stream >> image;
Run Code Online (Sandbox Code Playgroud)

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

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

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

这个例子是关于读写垂直xor编码的图像.格式非常简单.每个字节代表两个像素(每像素4位).每行与前一行xor'd.这种编码使图像准备好进行压缩(通常会导致大量的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;
}
Run Code Online (Sandbox Code Playgroud)

Die*_*ühl 59

创建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());
    }
};
Run Code Online (Sandbox Code Playgroud)

underflow()外观如何完全取决于所使用的压缩库.我使用的大多数库都保留了一个需要填充的内部缓冲区,它保留了尚未消耗的字节.通常,将解压缩挂钩相当容易underflow().

创建流缓冲区后,您只需std::istream使用流缓冲区初始化对象:

std::ifstream fin("some.file");
compressbuf   sbuf(fin.rdbuf());
std::istream  in(&sbuf);
Run Code Online (Sandbox Code Playgroud)

如果要经常使用流缓冲区,则可能需要将对象构造封装到类中,例如icompressstream.这样做有点棘手,因为基类std::ios是虚拟基础,并且是存储流缓冲区的实际位置.在将指针传递给a之前构造流缓冲区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_) {
    }
};
Run Code Online (Sandbox Code Playgroud)

(我只是输入了这段代码而没有一种简单的方法来测试它是否合理正确;请期待拼写错误,但整体方法应如所描述的那样工作)

  • 如果你在思考Java,那么`InputStream`比`std :: istream`更接近`std :: streambuf`.`std :: istream`更像是Java的`Format`,但是它的界面使得它更易于使用. (3认同)

Cub*_*bbi 9

boost(如果你认真对待C++,你应该已经拥有),有一个专门用于扩展和自定义IO流的整个库:boost.iostreams

特别是,它已经为一些流行的格式(bzip2,gzlibzlib)提供了解压缩流

如您所见,扩展streambuf可能是一项涉及工作,但是如果您需要,可以很容易地编写自己的过滤streambuf.

  • 是的,提升绝对是我必须学习的话题.但我想在我真正理解boost库的好处之前,先学习如何使用(或讨厌)标准C++类是个好主意. (3认同)