如何在 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 + + 中实现自定义输入/输出流(或者 Strebufs)的最佳方式是什么呢?

解决方案

一些人建议根本不要使用 iostream,而是使用迭代器、升级或自定义 IO 接口。这些可能是有效的选择,但我的问题是关于 iostream。接受的答案导致了下面的示例代码。为了更容易阅读,没有标题/代码分离,并且导入了整个 std 名称空间(我知道在实际代码中这是一件坏事)。

此示例是关于读取和写入垂直 xor 编码的图像的。格式很简单。每个字节表示两个像素(每个像素4位)。每一行都与前一行相异。这种编码为压缩图像做准备(通常会产生大量的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;
}
56560 次浏览

不要,除非你想死得很难看。IOstream 是 Standard 库中最糟糕的组件——甚至比 locale 还糟糕。迭代器模型更加有用,您可以使用 istream _ iterator 将流转换为迭代器。

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

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

这可能是可能的,但是我觉得这不是 C + + 中这个特性的“正确”用法。Iostream > 和 < < 操作符用于相当简单的操作,例如编写 class Person的“ name、 street、 town、 post code”,而不是用于解析和加载图像。最好使用流: : read ()-使用 Image(astream);,并且您可以实现一个用于压缩的流,正如 Dietmar 所描述的那样。

升级(如果你是认真对待 C + + 的话,你应该已经有了) ,有一个完整的库专门用于扩展和定制 IO 流: 加油

特别是,它已经有了几种流行格式(Bzip2GzlibZlib)的解压缩流

正如您所看到的,扩展 stream buf 可能是一项复杂的工作,但是如果您需要的话,这个库使 编写自己的过滤流缓冲器变得相当容易。

我同意@DeadMG 的观点,不建议使用 iostream。除了糟糕的设计,性能往往比普通的老式 C 风格的 I/O 更糟糕。我不会坚持使用特定的 I/O 库,相反,我会创建一个接口(抽象类) ,其中包含所有需要的操作,例如:

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

然后,您可以为 C I/O、 iostream、 mmap或其他任何东西实现这个接口。