如何在c++中编写自定义输入流
How to write custom input stream in 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
的提示。但是对于这样一个简单的任务,这个例子看起来非常复杂。
一些人建议根本不要使用iostreams,而使用迭代器、boost或自定义IO接口来代替。这些可能是有效的替代方案,但我的问题是关于iostreams的。接受的答案产生了下面的示例代码。为了更容易阅读,没有头/代码分离,并且导入了整个std命名空间(我知道这在实际代码中是一件坏事)。
这个例子是关于读取和写入垂直编码的图像。格式很简单。每个字节代表两个像素(每像素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;
}
标题>在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_) {
}
};
(我只是键入这段代码,没有一个简单的方法来测试它是合理正确的;
boost(如果你认真对待c++的话,你应该已经有了),有一个专门用于扩展和定制IO流的完整库:boost.iostreams
特别是,它已经为一些流行的格式(bzip2, gzlib和zlib)提供了解压缩流
如您所见,扩展streambuf可能是一项复杂的工作,但是如果需要的话,该库使编写您自己的过滤streambuf变得相当容易。不要,除非你想死于丑陋的设计。IOstreams是标准库中最糟糕的组件——甚至比locale还糟糕。迭代器模型更有用,可以使用istream_iterator将流转换为迭代器。
这样做是可能的,但我觉得这不是c++中使用该特性的"正确"方法。iostream>>和<<操作符用于相当简单的操作,例如写入class Person
的"名称、街道、城镇、邮政编码",而不是用于解析和加载图像。使用stream::read()——使用Image(astream);
,您可以实现用于压缩的流,正如Dietmar所描述的那样。
我同意@DeadMG的观点,不建议使用iostreams。除了糟糕的设计之外,性能通常比普通的老式c风格I/O更差。不过,我不会坚持使用特定的I/O库,而是创建一个接口(抽象类),其中包含所有必需的操作,例如:
class Input {
public:
virtual void read(char *buffer, size_t size) = 0;
// ...
};
然后你可以为C I/O, iostreams, mmap
或其他实现这个接口。
在阅读了几个STL参考文献和某些用例的示例解决方案之后,我仍然缺少一个说教性的答案,这很简单:
- 当
- 此方法将在
std::streambuf
的子类中重写,以删除读取缓冲区请求到特定的底层源流。 -
underflow
方法的post条件要么是' eof ',当源流结束时
,要么是后续有效输入数据的缓冲区窗口被定义为setg(buffer-begin, buffer-begin, buffer-end)
和返回的第一个字符。
std::istream
实例消耗了前面的read buffer内容时,调用std::streambuf::underflow()
方法。- C++ 中完全自定义的流运算符
- 编译 GPU 的张量流示例自定义操作
- 如何使用运算符>>在自定义字符串中输入多个单词?
- CLion 是否支持自定义输入?如果是,我在哪里输入它们?
- 在程序执行期间从标准输出重定向到自定义流
- C++自定义流操纵器,用于更改流上的下一个字符串
- 如何通过自定义的流设置流的坏位
- 字符串流中的自定义字符串输入
- 自定义张量流操作获取可变输入张量的列表
- 运算符 -> 不适用于自定义输入迭代器
- 可以在程序退出时和请求输入时自动刷新自定义流缓冲区
- 用于自定义类型的自定义c++流
- 我如何从自定义数据流编写CoreGraphics CGImageRef
- 虚幻引擎4创建自定义输入设备插件
- 如何在c++中提供自定义输出流
- 创建自定义std流实现时的编译器警告
- 自定义缓冲输入流.输入结束
- 自定义输入流.流缓冲和底流法
- Boost Wave自定义输入策略
- 如何在c++中编写自定义输入流