container_device
Suppose you want to write a Device for reading from and writing to an STL container. In order for combined reading and writing to be useful, you will also need to support seeking within the container. There are several types of Devices which combine reading and writing; they differ according to whether there are two separate character sequences for input and output, or a single combined sequence, and whether there are separate position indicators for reading and writing or a single read/write position indicator. See Modes for details.
A narrow-character Device for read-write access to a single character sequence with a single position indicator is called a SeekableDevice. A typical SeekableDevice looks like this:
#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 */ };
Here the member type char_type
indicates the type of characters handled by my_source, which will almost always be char
or wchar_t
. The member type category indicates which of the fundamental i/o operations are supported by the device. The category tag seekable_tag
indicates that read
, write
and the single-sequence version of seek
are supported.
The type stream_offset
is used by the Iostreams library to hold stream offsets.
You could also write the above example as follows:
#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 */ };
Here seekable_device
is a convenience base class which provides the member types char_type
and category
, as well as no-op implementations of member functions close
and imbue
, not needed here.
You're now ready to write your container_device
. Again, let's assume your container's iterators are 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_); result = (min)(n, amt); std::copy(s, s + result, container_.begin() + pos_); pos_ += result; } if (result < n) { container_.insert(container_.end(), s + result, 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_base::beg) { next = off; } else if (way == ios_base::cur) { next = pos_ + off; } else if (way == ios_base::end) { next = container_.size() + off - 1; } else { throw ios_base::failure("bad seek direction"); } // Check for errors if (next < 0 || next >= static_cast<stream_offset>(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
Here, note that
char_type
is defined to be equal to the container's value_type
;
category
tells the Iostreams library that container_device
is a model of SeekableDevice;
container_device
can be constructed from a instance of Container
, which is passed and stored by reference, and accessible via the member function container()
;
read
is identical to the implementation in container_source
.
The implementation of write
is a bit different from the implementation in container_sink
. Rather than simply appending characters to the container, you first check whether the current character position is somewhere in the middle of the container. If it is, you attempt to satisfy the write request by overwriting existing characters in the container, starting at the current character position. If you reach the end of the container before the write request is satisfied, you insert the remaining characters at the end.
The implementation of seek
is straightforward. First, you calculate the new character position based on off
and way
: if way
is ios_base::beg
, the new character position is simply off
; if way
is ios_base::cur
, the new character position is pos_ + off
; if way
is ios_base::end
, the new character position is container_.size() + off - 1
. Next, you check whether the new character position is a valid offset, and throw an exception if it isn't. Instances of std::basic_streambuf
are allowed to return -1
to indicate failure, but the policy of the Boost Iostreams library is that errors should be indicated by throwing an exception (see Exceptions). Finally, you set the new position and return it.
You can use a container_device as follows
#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!"); }
© Copyright 2008 CodeRage, LLC
© Copyright 2004-2007 Jonathan Turkanis
Use, modification, and distribution are subject to the Boost Software License, Version 2.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)