boost.iostreams指南--使用Devices
1.1. 概要: Devices, stream_buffer and stream
使用Boost Iostreams库能很容易创建一个stream类: 你只需简单的写一个类,在类中构造Device概念的模型,并将这个类作为模板参数传给stream或者stream_buffer。
#include
<
boost
/
iostreams
/
stream.hpp
>
#include
<
boost
/
iostreams
/
stream_buffer.hpp
>

namespace
io
=
boost::iostreams;

class
my_device
{ /* */ }
;

typedef io::stream
<
my_device
>
my_stream;
typedef io::stream_buffer
<
my_device
>
my_streambuf;
在这里io::stream_buffer<my_device>是std::basic_streambuf的继承类,io::stream<my_device>是std::basic_istream的继承类,至于是std::basic_ostream还是std::basic_iostream将取决于my_device的模式,例如对应的i/o读写操作都将依赖于my_device。
#include
<
ostream
>
#include
<
boost
/
iostreams
/
device
/
file.hpp
>
#include
<
boost
/
iostreams
/
stream.hpp
>

namespace
io
=
boost::iostreams;

int
main()
{
io::stream_buffer<file_sink> buf("log.txt");
std::ostream out(&buf);
// out writes to log.txt
}
在这个例子红, ostream将stream_buffer buf当作它的底层数据sink进行输出, 因此,写入到out中的数据都会被转到log.txt. 使用默认构造函数创建out并通过out.rdbuf(&buf)使用stream buffer buf 也可以获得同样的效果。.
可以通过其他方式使用Boost Iostreams库定义一个新的stream或者stream buffer类,例如从filtering_stream或者filtering_streambuf派生。
1.2. 使用一个container_source
假设你想写一个 Device来从STL容器中读取字符。一个只支持读取操作的Device被成为一个Source。一个典型的单字符Source如下所示:
#include
<
iosfwd
>
//
streamsize
#include
<
boost
/
iostreams
/
categories.hpp
>
//
source_tag
namespace
io
=
boost::iostreams;

class
my_source
{
public:
typedef char char_type;
typedef source_tag category;

std::streamsize read(char* s, std::streamsize n)
{
// Read up to n characters from the underlying data source
// into the buffer s, returning the number of characters
// read; return -1 to indicate EOF
}

/* Other members */
}
;
在这里成员类型 char_type 表明 my_source处理字符类型, Source通常也是处理char和wchar_t. 成员类型category表明 device支持的基础I/O操作. category标记source_tag 表明只有读操作被支持.
成员函数read 读取最多n个字符到buffer s中,并返回读取的字符数目,当读取字符数目为0并且到达end-of-stream时将返回-1。在通常情况下,一个Source的成员函数read读取的字符数可能比要求读取的数目少,即使没有到达end-of-stream,这种类型的Source被称为非阻塞Source。 非阻塞Devices与标准streams和stream buffers不能很协调相互配合,但是绝大多数devices是阻塞型的。
你可以将上面的例子改下如下:
#include
<
boost
/
iostreams
/
concepts.hpp
>
//
source
class
my_source :
public
source
{
public:
std::streamsize read(char* s, std::streamsize n);

/* Other members */
}
;
这里的source是一个方便的基类,它提供了成员类型char_type和category。
现在,你准备去写你的container-source。为了简明一些,我们假设你的容器的迭代器是RandomAccessIterators。
#include
<
algorithm
>
//
copy, min
#include
<
iosfwd
>
//
streamsize
#include
<
boost
/
iostreams
/
categories.hpp
>
//
source_tag
namespace
boost
{ namespace iostreams { namespace example {

template<typename Container>
class container_source {
public:
typedef typename Container::value_type char_type;
typedef source_tag category;
container_source(Container& container)
: container_(container), pos_(0)
{ }
std::streamsize read(char_type* s, std::streamsize n)
{
using namespace std;
streamsize amt = static_cast<streamsize>(container_.size() - pos_);
streamsize result = (min)(n, amt);
if (result != 0) {
std::copy( container_.begin() + pos_,
container_.begin() + pos_ + result,
s );
pos_ += result;
return result;
} else {
return -1; // EOF
}
}
Container& container() { return container_; }
private:
typedef typename Container::size_type size_type;
Container& container_;
size_type pos_;
};

} } }
//
End namespace boost::iostreams:example
这里有几点需要注意
* 成员type char_type被定义为容器的value_type;
*成员类型category 告诉 Iostreams库container_source是一个Source模型;
*一个container_source可以从一个Container实例构造出来。
read()的实现所基于的思想很简单:首先通过计算Container中剩余字符的数目和要求读取数目的最小值来设置被读取的数目, 接着,如果被读取的数目不为0, 从容器中读取相应数目的字符到缓冲区中 并更新从容器中读取的位置.如果读取数目为0,例如,如果所有容器中所有数据都被读取过 (或者容器为空), 返回-1以指示end-of-stream。
你可以参考下例所示来使用一个container_source:
#include
<
cassert
>
#include
<
string
>
#include
<
boost
/
iostreams
/
stream.hpp
>
#include
<
libs
/
iostreams
/
example
/
container_device.hpp
>
//
container_source
namespace
io
=
boost::iostreams;
namespace
ex
=
boost::iostreams::example;

int
main()
{
using namespace std;
typedef ex::container_source<string> string_source;

string input = "Hello World!";
string output;
io::stream<string_source> in(input);
getline(in, output);
assert(input == output);
}
最后,我需要声明的一点是Iostreams库提供了一种从STL容器中读取数据的更方便的方式:一个boost::iterator_range实例可以直接被filtering streams以及stream buffers使用,如下列所示:
#include
<
cassert
>
#include
<
string
>
#include
<
boost
/
iostreams
/
filtering_stream.hpp
>
#include
<
boost
/
range
/
iterator_range.hpp
>

namespace
io
=
boost::iostreams;

int
main()
{
using namespace std;

string input = "Hello World!";
string output;
io::filtering_istream in(boost::make_iterator_range(input));
getline(in, output);
assert(input == output);
}
1.3.使用一个container_sink
假设你想写一个 Device来向STL容器中添加字符。一个只支持写入操作的Device被成为一个Sink。一个典型的单字符Sink如下所示:
#include
<
iosfwd
>
//
streamsize
#include
<
boost
/
iostreams
/
categories.hpp
>
//
sink_tag
namespace
io
=
boost::iostreams;

class
my_sink
{
public:
typedef char char_type;
typedef sink_tag category;

std::streamsize write(const char* s, std::streamsize n)
{
// Write up to n characters to the underlying
// data sink into the buffer s, returning the
// number of characters written
}

/* Other members */
}
;
此处的成员类型 char_type 表明 my_sink处理字符类型, Sink通常也是处理char和wchar_t. 成员类型category表明 device支持的基础I/O操作. category标记sink_tag 表明只有写操作被支持.
成员函数write写入最多n个字符到buffer s中,并返回写入的字符数目。在通常情况下,一个Sink的成员函数write写入的字符数可能比要求写入的数目少,这种类型的Sink被称为非阻塞Sink。 非阻塞Devices与标准streams和stream buffers不能很协调相互配合,但是绝大多数devices是阻塞型的。
你可以将上面的例子改下如下:
#include
<
boost
/
iostreams
/
concepts.hpp
>
//
sink
class
my_sink :
public
sink
{
public:
std::streamsize write(const char* s, std::streamsize n);

/* Other members */
}
;
此处的sink是一个方便的基类,它提供了成员类型char_type和category。
现在,你准备去写你的container-sink。
#include
<
algorithm
>
//
copy, min
#include
<
iosfwd
>
//
streamsize
#include
<
boost
/
iostreams
/
categories.hpp
>
//
sink_tag
namespace
boost
{ namespace iostreams { namespace example {

template<typename Container>
class container_sink {
public:
typedef typename Container::value_type char_type;
typedef sink_tag category;
container_sink(Container& container) : container_(container) { }
std::streamsize write(const char_type* s, std::streamsize n)
{
container_.insert(container_.end(), s, s + n);
return n;
}
Container& container() { return container_; }
private:
Container& container_;
};

} } }
//
End namespace boost::iostreams:example
这里有几点需要注意
* 成员type char_type被定义为容器的value_type;
*成员类型category 告诉 Iostreams库container_sink是一个Sink模型;
*一个container_sink可以从一个Container实例构造出来。
* write()简单的将指定缓冲的字符使用insert添加到容器中。
你可以使用一个container_sink如下:
#include
<
cassert
>
#include
<
string
>
#include
<
boost
/
iostreams
/
stream.hpp
>
#include
<
libs
/
iostreams
/
example
/
container_device.hpp
>
//
container_source
namespace
io
=
boost::iostreams;
namespace
ex
=
boost::iostreams::example;

int
main()
{
using namespace std;
typedef ex::container_sink<string> string_sink;

string result;
io::stream<string_sink> out(result);
out << "Hello World!";
out.flush();
assert(result == "Hello World!");
}
注意Iostreams默认支持缓冲,the stream out必须flushed ,然后被写入的字符才会出现在内部的string中。
最后,我需要声明的一点是Iostreams库提供了几种向STL容器中写入数据的更方便的方式:
第一,OutputInterators可以直接被filtering streams以及stream buffers使用,如下列所示:
#include
<
cassert
>
#include
<
iterator
>
//
back_inserter
#include
<
string
>
#include
<
boost
/
iostreams
/
filtering_stream.hpp
>

namespace
io
=
boost::iostreams;

int
main()
{
using namespace std;

string result;
io::filtering_ostream out(back_inserter(result));
out << "Hello World!";
out.flush();
assert(result == "Hello World!");
}
第二,Iostreams库提供一种back_inserter,它由于使用insert而不是push_back,在某种程度上要比std::back_inserter效率更高。
#include
<
cassert
>
#include
<
string
>
#include
<
boost
/
iostreams
/
device
/
back_inserter.hpp
>
#include
<
boost
/
iostreams
/
filtering_stream.hpp
>

namespace
io
=
boost::iostreams;

int
main()
{
using namespace std;

string result;
io::filtering_ostream out(io::back_inserter(result));
out << "Hello World!";
out.flush();
assert(result == "Hello World!");
}
1.4. 使用 container_device
假设你想写一个 Device来从STL容器中读取字符记忆向容器中写入字符。为了使读和写实用,你还支持对容器的定位。有许多种Devices能同时支持读和写;他们直接也存在区别,有些使用两个独立的序列支持输入和输出,有些使用单独的序列支持输入和输出;有些有两个单独的读写定位符,有些只有一个定位符来。细节部分可以参考Modes
一个使用单个定位符支持读写单字符序列的单字符Devics被称为一个SeekableDevice。一个典型的SeekableDevice如下所示:
#include
<
iosfwd
>
//
streamsize, seekdir
#include
<
boost
/
iostreams
/
categories.hpp
>
//
seekable_device_tag
#include
<
boost
/
iostreams
/
positioning.hpp
>
//
stream_offset
namespace
io
=
boost::iostreams;

class
my_device
{
public:
typedef char char_type;
typedef seekable_device_tag category;

std::streamsize read(char* s, std::streamsize n)
{
// Read up to n characters from the underlying data source
// into the buffer s, returning the number of characters
// read; return -1 to indicate EOF
}

std::streamsize write(const char* s, std::streamsize n)
{
// Write up to n characters to the underlying
// data sink into the buffer s, returning the
// number of characters written
}

stream_offset seek(stream_offset off, std::ios_base::seekdir way)
{
// Seek to position off and return the new stream
// position. The argument way indicates how off is
// interpretted:
// - std::ios_base::beg indicates an offset from the
// sequence beginning
// - std::ios_base::cur indicates an offset from the
// current character position
// - std::ios_base::end indicates an offset from the
// sequence end
}

/* Other members */
}
;
此处的类型 char_type 说明了my_source处理的字符类型, my_source通常只处理单字符和宽字符,成员类型category说明了device支持的i/o操作。 category标志seekable_tag说明read, write以及单序列版本的seek操作都是被支持的.
类型stream_offset在Iostreams库中用来保存stream offsets.
你可以将上面的例子改写如下:
#include
<
boost
/
iostreams
/
concepts.hpp
>
//
seekable_device
class
my_source :
public
seekable_device
{
public:
std::streamsize read(char* s, std::streamsize n);
std::streamsize write(const char* s, std::streamsize n);
stream_offset seek(stream_offset off, std::ios_base::seekdir way);

/* Other members */
}
;
此处的seekable_device是一个非常方便的基类,它提供了成员类型char_type、category, 以及上例没有使用的成员函数close和imbue。
现在,你准备去写你的 container_device。同样,我们假设你的容器的迭代器是RandomAccessIterators类型。
#include
<
algorithm
>
//
copy, min
#include
<
iosfwd
>
//
streamsize
#include
<
boost
/
iostreams
/
categories.hpp
>
//
source_tag
namespace
boost
{ namespace iostreams { namespace example {

template<typename Container>
class container_device {
public:
typedef typename Container::value_type char_type;
typedef seekable_device_tag category;
container_device(Container& container)
: container_(container), pos_(0)
{ }

std::streamsize read(char_type* s, std::streamsize n)
{
using namespace std;
streamsize amt = static_cast<streamsize>(container_.size() - pos_);
streamsize result = (min)(n, amt);
if (result != 0) {
std::copy( container_.begin() + pos_,
container_.begin() + pos_ + result,
s );
pos_ += result;
return result;
} else {
return -1; // EOF
}
}
std::streamsize write(const char_type* s, std::streamsize n)
{
using namespace std;
streamsize result = 0;
if (pos_ != container_.size()) {
streamsize amt =
static_cast<streamsize>(container_.size() - pos_);
streamsize result = (min)(n, amt);
std::copy(s, s + result, container_.begin() + pos_);
pos_ += result;
}
if (result < n) {
container_.insert(container_.end(), s, s + n);
pos_ = container_.size();
}
return n;
}
stream_offset seek(stream_offset off, std::ios_base::seekdir way)
{
using namespace std;

// Determine new value of pos_
stream_offset next;
if (way == ios_bas::beg) {
next = off;
} else if (way == ios_bas::cur) {
next = pos_ + off;
} else if (way == ios_bas::end) {
next = container_.size() + off - 1;
}

// Check for errors
if (next < 0 || next >= container_.size())
throw ios_base::failure("bad seek offset");

pos_ = next;
return pos_;
}

Container& container() { return container_; }
private:
typedef typename Container::size_type size_type;
Container& container_;
size_type pos_;
};

} } }
//
End namespace boost::iostreams:example
此处有几点需要注意
* 成员type char_type被定义为容器的value_type;
*成员类型category 告诉 Iostreams库container_device是一个SeekableDevice模型;
*一个container_device可以从一个Container实例构造出来。
* read 函数的实现和其在container_source的实现是一样的。
write 函数的实现和其在container_sink中的实现有一点区别。不光是简单地将字符添加到容器中,你首先要确认当前字符位置是在容器中间的某个位置。如果是的话,那么你写入数据的同时,将会覆盖掉容器中从当前位置开始的已有的字符。如果在写入数据时,你处于容器尾部,那么你将在尾部插入剩下的字符。
seek的实现是比较简单的(省略不说)。
你可以如下列所示使用container_device
#include
<
cassert
>
#include
<
ios
>
//
ios_base::beg
#include
<
string
>
#include
<
boost
/
iostreams
/
stream.hpp
>
#include
<
libs
/
iostreams
/
example
/
container_device.hpp
>

namespace
io
=
boost::iostreams;
namespace
ex
=
boost::iostreams::example;

int
main()
{
using namespace std;
typedef ex::container_device<string> string_device;

string one, two;
io::stream<string_device> io(one);
io << "Hello World!";
io.flush();
io.seekg(0, BOOST_IOS::beg); // seek to the beginning
getline(io, two);
assert(one == "Hello World!");
assert(two == "Hello World!");
}
1.1. 概要: Devices, stream_buffer and stream
使用Boost Iostreams库能很容易创建一个stream类: 你只需简单的写一个类,在类中构造Device概念的模型,并将这个类作为模板参数传给stream或者stream_buffer。









在这里io::stream_buffer<my_device>是std::basic_streambuf的继承类,io::stream<my_device>是std::basic_istream的继承类,至于是std::basic_ostream还是std::basic_iostream将取决于my_device的模式,例如对应的i/o读写操作都将依赖于my_device。












在这个例子红, ostream将stream_buffer buf当作它的底层数据sink进行输出, 因此,写入到out中的数据都会被转到log.txt. 使用默认构造函数创建out并通过out.rdbuf(&buf)使用stream buffer buf 也可以获得同样的效果。.
可以通过其他方式使用Boost Iostreams库定义一个新的stream或者stream buffer类,例如从filtering_stream或者filtering_streambuf派生。
1.2. 使用一个container_source
假设你想写一个 Device来从STL容器中读取字符。一个只支持读取操作的Device被成为一个Source。一个典型的单字符Source如下所示:



















在这里成员类型 char_type 表明 my_source处理字符类型, Source通常也是处理char和wchar_t. 成员类型category表明 device支持的基础I/O操作. category标记source_tag 表明只有读操作被支持.
成员函数read 读取最多n个字符到buffer s中,并返回读取的字符数目,当读取字符数目为0并且到达end-of-stream时将返回-1。在通常情况下,一个Source的成员函数read读取的字符数可能比要求读取的数目少,即使没有到达end-of-stream,这种类型的Source被称为非阻塞Source。 非阻塞Devices与标准streams和stream buffers不能很协调相互配合,但是绝大多数devices是阻塞型的。
你可以将上面的例子改下如下:








这里的source是一个方便的基类,它提供了成员类型char_type和category。
现在,你准备去写你的container-source。为了简明一些,我们假设你的容器的迭代器是RandomAccessIterators。





































这里有几点需要注意
* 成员type char_type被定义为容器的value_type;
*成员类型category 告诉 Iostreams库container_source是一个Source模型;
*一个container_source可以从一个Container实例构造出来。
read()的实现所基于的思想很简单:首先通过计算Container中剩余字符的数目和要求读取数目的最小值来设置被读取的数目, 接着,如果被读取的数目不为0, 从容器中读取相应数目的字符到缓冲区中 并更新从容器中读取的位置.如果读取数目为0,例如,如果所有容器中所有数据都被读取过 (或者容器为空), 返回-1以指示end-of-stream。
你可以参考下例所示来使用一个container_source:



















最后,我需要声明的一点是Iostreams库提供了一种从STL容器中读取数据的更方便的方式:一个boost::iterator_range实例可以直接被filtering streams以及stream buffers使用,如下列所示:


















1.3.使用一个container_sink
假设你想写一个 Device来向STL容器中添加字符。一个只支持写入操作的Device被成为一个Sink。一个典型的单字符Sink如下所示:



















此处的成员类型 char_type 表明 my_sink处理字符类型, Sink通常也是处理char和wchar_t. 成员类型category表明 device支持的基础I/O操作. category标记sink_tag 表明只有写操作被支持.
成员函数write写入最多n个字符到buffer s中,并返回写入的字符数目。在通常情况下,一个Sink的成员函数write写入的字符数可能比要求写入的数目少,这种类型的Sink被称为非阻塞Sink。 非阻塞Devices与标准streams和stream buffers不能很协调相互配合,但是绝大多数devices是阻塞型的。
你可以将上面的例子改下如下:








此处的sink是一个方便的基类,它提供了成员类型char_type和category。
现在,你准备去写你的container-sink。























这里有几点需要注意
* 成员type char_type被定义为容器的value_type;
*成员类型category 告诉 Iostreams库container_sink是一个Sink模型;
*一个container_sink可以从一个Container实例构造出来。
* write()简单的将指定缓冲的字符使用insert添加到容器中。
你可以使用一个container_sink如下:



















注意Iostreams默认支持缓冲,the stream out必须flushed ,然后被写入的字符才会出现在内部的string中。
最后,我需要声明的一点是Iostreams库提供了几种向STL容器中写入数据的更方便的方式:
第一,OutputInterators可以直接被filtering streams以及stream buffers使用,如下列所示:

















第二,Iostreams库提供一种back_inserter,它由于使用insert而不是push_back,在某种程度上要比std::back_inserter效率更高。

















1.4. 使用 container_device
假设你想写一个 Device来从STL容器中读取字符记忆向容器中写入字符。为了使读和写实用,你还支持对容器的定位。有许多种Devices能同时支持读和写;他们直接也存在区别,有些使用两个独立的序列支持输入和输出,有些使用单独的序列支持输入和输出;有些有两个单独的读写定位符,有些只有一个定位符来。细节部分可以参考Modes
一个使用单个定位符支持读写单字符序列的单字符Devics被称为一个SeekableDevice。一个典型的SeekableDevice如下所示:









































此处的类型 char_type 说明了my_source处理的字符类型, my_source通常只处理单字符和宽字符,成员类型category说明了device支持的i/o操作。 category标志seekable_tag说明read, write以及单序列版本的seek操作都是被支持的.
类型stream_offset在Iostreams库中用来保存stream offsets.
你可以将上面的例子改写如下:










此处的seekable_device是一个非常方便的基类,它提供了成员类型char_type、category, 以及上例没有使用的成员函数close和imbue。
现在,你准备去写你的 container_device。同样,我们假设你的容器的迭代器是RandomAccessIterators类型。













































































此处有几点需要注意
* 成员type char_type被定义为容器的value_type;
*成员类型category 告诉 Iostreams库container_device是一个SeekableDevice模型;
*一个container_device可以从一个Container实例构造出来。
* read 函数的实现和其在container_source的实现是一样的。
write 函数的实现和其在container_sink中的实现有一点区别。不光是简单地将字符添加到容器中,你首先要确认当前字符位置是在容器中间的某个位置。如果是的话,那么你写入数据的同时,将会覆盖掉容器中从当前位置开始的已有的字符。如果在写入数据时,你处于容器尾部,那么你将在尾部插入剩下的字符。
seek的实现是比较简单的(省略不说)。
你可以如下列所示使用container_device






















