我目前正在学习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
的提示。但是这个example对于这样一个简单的任务来说看起来非常复杂。
那么,实现自定义输入/输出流(或streambufs)的最佳方法是什么呢?)在C++?
溶液
有些人建议不要使用iostreams,而是使用迭代器,boost或自定义IO接口。这些可能是有效的替代方案,但我的问题是关于iostreams的。接受的答案导致下面的示例代码。为了更容易阅读,没有头/代码分离,整个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;
}
6条答案
按热度按时间agxfikkp1#
在C++中创建新流的正确方法是从
std::streambuf
派生,并覆盖underflow()
的阅读操作和overflow()
和sync()
的写操作。对于您的目的,您将创建一个过滤流缓冲区,它以另一个流缓冲区(可能是一个可以使用rdbuf()
从中提取流缓冲区的流)作为参数,并根据此流缓冲区实现自己的操作。流缓冲区的基本轮廓如下所示:
underflow()
的外观取决于所使用的压缩库。我使用过的大多数库都有一个内部缓冲区,需要填充它,它会保留尚未使用的字节。通常,将解压缩挂接到underflow()
中是相当容易的。一旦创建了流缓冲区,你就可以用流缓冲区初始化一个
std::istream
对象:如果您要经常使用流缓冲区,您可能希望将对象构造封装到一个类中,例如例如,
icompressstream
。这样做有点棘手,因为基类std::ios
是一个虚拟基类,是存储流缓冲区的实际位置。因此,要在将指针传递到std::ios
之前构造流缓冲区,需要跳过几个环:它需要使用virtual
基类。以下是它的大致情况:(我只是输入了这段代码,没有一个简单的方法来测试它是否合理正确;请期待错别字,但整体方法应按所述工作)
siotufzp2#
boost(如果你对C++很认真的话,你应该已经有了),有一个完整的库专门用于扩展和定制IO流:boost.iostreams
特别是,它已经为一些流行的格式(bzip2、gzlib和zlib)提供了解压缩流
正如您所看到的,扩展streambuf可能是一项复杂的工作,但是如果您需要的话,这个库使write your own filtering streambuf变得相当容易。
elcex8rz3#
别这样,除非你想死在丑陋的设计中。IOstreams是标准库中最糟糕的组件--甚至比locale还糟糕。迭代器模型要有用得多,可以使用istream_iterator将流转换为迭代器。
xhv8bpkk4#
也许可以这样做,但我觉得这不是C++中这个特性的“正确”用法。iostream〉〉和〈〈操作符用于相当简单的操作,例如写入
class Person
的“名称,街道,城镇,邮政编码”,而不是用于解析和加载图像。使用stream::read()-使用Image(astream);
-可以更好地完成这一点,并且您可以实现一个用于压缩的流,如Dietmar所述。2w3kk1z55#
我同意@DeadMG的观点,不建议使用iostreams。除了糟糕的设计之外,性能通常比普通的老式C风格I/O更差。我不会坚持使用特定的I/O库,相反,我会创建一个具有所有必需操作的接口(抽象类),例如:
然后,您可以为C I/O、iostreams、
mmap
或其他任何对象实现此接口。xmakbtuz6#
在阅读了一些STL参考文献和某些用例的示例解决方案之后,我仍然缺少一个教学性的答案,这是非常简单的:
1.当
std::istream
示例已经消耗了前面的 * 读缓冲区 * 内容时,调用方法std::streambuf::underflow()
。1.此方法将在
std::streambuf
的子类中被覆盖,以将 read buffer 请求委托给特定的底层源流。underflow
方法的post条件是'eof',当源流结束时或者,有效输入数据的后续缓冲区窗口由
setg(buffer-begin, buffer-begin, buffer-end)
定义,并返回其第一个字符。