【发布时间】:2012-12-14 17:12:15
【问题描述】:
我目前正在学习 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 看起来非常复杂。
那么在 C++ 中实现自定义输入/输出流(或 streambufs?)的最佳方式是什么?
解决方案
有些人建议根本不使用 iostream,而是使用迭代器、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;
}
【问题讨论】:
-
我强烈建议避免使用 iostream。请参阅stackoverflow.com/questions/2753060/…、accu.org/index.php/journals/1539 和google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams 了解其中的一些原因。
-
@vitaut:如果我正确理解了 Google 风格指南,那么他们推荐使用旧的 C 风格 I/O 吗?但是我不明白我如何从我的类中抽象出 I/O。我的 Image 类只想读取数据,它不想关心数据源或者数据源是否被压缩或加密等等。使用旧的 C 风格 I/O,我可以将文件句柄传递给它,仅此而已。听起来不是一个好的选择。
-
按照 DeadMG 的建议,您可以使用迭代器。或者您可以创建一个简单的接口(抽象类)来定义您需要的一些操作,例如您提到的 read()。然后你可以有你的接口的几个实现,例如一个使用 C 风格的 I/O,或 mmap 或其他任何东西,甚至是 iostream。
-
问题:你会传入像 std::cout 这样的标准流作为构造函数的 streambuf 参数吗?
-
我认为问题解决方案中给出的 main() 有一个小但关键的错误。 ifstream 和 ofstream 应该以二进制模式打开: ``` int main() { // 读取数据 ifstream infile("test.img", ios::binary); ... // 写入数据流 outfile("test2.img", ios::binary); ... } ``` 如果没有这个,我发现文件的读取在 Windows 上过早结束(我会将此作为评论添加,但我还没有 50 名声望)