Suppose we want to define a standard stream buffer to read from a new type of input device. Typically this means deriving a new class from a specialization of std::basic_streambuf and overriding the protected virtual function underflow, and possibly also uflow and xsgetn. If we wish to be able to put back characters to the stream buffer, we also need to override pbackfail. If we wish to add buffering, we need to allocate a buffer and use the buffer manipulation functions setg, eback, gptr, egptr and gbump.
This process can be confusing to those who do not routinely work with the protected virtual interface of std::basic_streambuf. More importantly, most of the work involved has very little to do with the new
data source we wish to access. Very often, the relevant behavior of a new data source can be represented by a single member
function, prototyped as follows:
std::streamsize read(char* s, std::streamsize n);
The function reads up to n characters into the buffer s, returning the number of characters read and throwing exceptions to indicate errors. If an integer less than n is
returned, the end of the stream has been reached.
The Iostreams Library allows users to create a standard input stream or stream buffer by defining a classes
with a single member function read and several member types.
Buffering and the ability to put back characters come for free. For a simple example, suppose we want to define a stream buffer to read a random sequence of characters.
We first define a Source as follows:
#include <cstdlib> #include <boost/iostreams/concepts.hpp> // source using namespace std; using namespace boost::io; struct random_source : public source { random_source(unsigned int seed = 0) { srand(seed); } std::streamsize read(char* s, std::streamsize n) { for (streamsize z = 0; z < n; ++z) s[z] = (char) (rand() * 256 / RAND_MAX); return n; } };
Here source is a convenience base class which provides several
member types as well as default implementations of several member functions. (The member functions are not needed here.) Given the above definition, we can construct a standard stream buffer as follows:
streambuf_facade<random_source> in(random_source(5)); ... // read from stream in.close(); // close stream in.open(random_source(634875)); // use a new seed ... // read from stream
We can also define a standard input stream:
stream_facade<random_source> in(random_source(5));
Both streambuf_facade and stream_facade have constructors and overloads of open which forward their arguments to Filter or Device constructor. Therefore, the first example could have been written:
streambuf_facade<random_source> in(5); ... in.close(); in.open(634875); ...
The case is similar if we wish to define a standard stream buffer to write to a new type of output device. Instead of
overriding the protected virtual functions overflow, xsputn and sync and making use of the buffer manipulation functions setp, pbase, pptr, epptr and pbump, we simply define a class with several member types and a member function write prototyped as follows:
void write(const char* s, std::streamsize n);
The function writes n characters to the output device, throwing exceptions to indicate errors.
For a simple example, suppose we want to define a stream buffer to write characters to the end of a standard vector. We can define a Sink as follows:
#include <vector> #include <boost/iostreams/concepts.hpp> // sink using namespace std; using namespace boost::io; struct vector_sink : public sink { vector_sink(vector<char>& vec) : vec(vec) { } void write(const char* s, std::streamsize n) { vec.insert(vec.end(), s, s + n); } vector<char>& vec; };
Here sink is a convenience base class which provides several
member types as well as default implementations of several member functions. (The member functions are not needed here.) Given the above definition, we can construct a standard stream buffer as follows:
vector<char> v; streambuf_facade<vector_sink> out(vector_sink(v)); // stream buffer which appends to v
We can also define a standard output stream:
stream_facade<vector_sink> out(vector_sink(v));
It's tempting to write:
vector<char> v; streambuf_facade<vector_sink> out(v); // error!!
Unfortunately, constructor-argument-forwarding only works for constuctors which take their arguments by value or by const reference; the vector_sink constructor takes a non-const reference.[1]
For easier ways to append to STL sequences, see STL Sequence Adapters.
Suppose we want to filter data read from a standard input stream. Let us say we wish to read only the alphabetic characters. We can do this by defining a component — an InputFilter — which reads data from a provided Source and modifies it before returning it to the user:
#include <ctype.h> // isalpha #include <cstdio> // EOF #include <boost/iostreams/concepts.hpp> // input_filter #include <boost/iostreams/operations.hpp> // get using namespace std; using namespace boost::io; struct alphabetic_input_filter : public input_filter { template<typename Source> int get(Source& src) { int c; while ((c = boost::iostreams::get(src)) != EOF && !isalpha(c)) ; return c; } };
Here input_filter is a convenience base class which provides several member types as well as default implementations of several member functions. (The member functions are not needed here.) The function boost::iostreams::get reads a character from an arbitrary Source; it is provided by the Iostreams Library to allow all Sources to be accessed with a single interface.[2] Given the above definition, we can read filtered data from standard input as follows:
filtering_streambuf<input> in;
in.push(alphabetic_input_filter());
in.push(cin);
The last item added to the chain could be any Source. E.g., we could read a random stream of alpabetic characters using the class random_source defined above:
filtering_streambuf<input> in;
in.push(alphabetic_input_filter());
in.push(random_source());
We could also add any number of InputFilters to the chain before adding the Source.
These examples all remain valid if filtering_stream<input> is substituted everywhere for filtering_streambuf<input>. They could also use the following convenience typedefs:
typedef filtering_streambuf<input> filtering_istreambuf; typedef filtering_stream<input> filtering_istream;
Suppose we want to filter data written to a standard output stream. Let us say we wish to transform each alphabetic character to upper case. We can do this by defining a component — an OutputFilter — which modifies data provided by the user before writing it to a given Sink:
#include <ctype.h> // toupper #include <boost/iostreams/concepts.hpp> // output_filter #include <boost/iostreams/operations.hpp> // put using namespace std; using namespace boost::io; struct toupper_output_filter : public output_filter { template<typename Sink> void put(Sink& snk, char c) { boost::iostreams::put(snk, toupper(c)); } };
Here output_filter is a convenience base class which provides several member types as well as default implementations of several member functions. (The member functions are not needed here.) The function boost::iostreams::put writes a character to an arbitrary Sink; it is provided by the Iostreams Library to allow all Sinks to be accessed with a single interface.[3] Given the above definition, we can write filtered data to standard output as follows:
filtered_streambuf<output> out;
out.push(toupper_output_filter());
out.push(cout);
The last item added to the chain could be any Sink. E.g., we could append uppercase characters to a standard vector using the class vector_sink defined above:
vector<char> v;
filtered_streambuf<output> out;
out.push(toupper_output_filter());
out.push(vector_sink(v));
We could also add any number of OutputFilters to the chain before adding the Sink.
These examples all remain valid if filtering_stream<output> is substituted everywhere for filtering_streambuf<output>. They could also use the following convenience typedefs:
typedef filtering_streambuf<output> filtering_ostreambuf; typedef filtering_stream<output> filtering_ostream;
A complex filtering operation will often not be inlinable. In such cases, each time a character is
read from an ordinary InputFilter one incurs the overhead of a call to
its member function get. To avoid this overhead, the library allows
users to define Multi-Character Filters which can process several characters
at once by implementing a member function read. This function will typically be called only when the buffer associated with the Filter by the Iostreams Library becomes full. For example, the following is a multi-character implementation of the alphabetic_input_filter described above.
#include <ctype.h> // isalpha #include <cstdio> // EOF #include <boost/iostreams/concepts.hpp> // multi_char_input_filter #include <boost/iostreams/operations.hpp> // get using namespace std; using namespace boost::io; struct alphabetic_input_filter : public multi_char_input_filter { template<typename Source> streamsize read(Source& src, char* s, streamsize n) { int c; char* first = s; char* last = s + n; while ( first != last && (c = boost::iostreams::get(src)) != EOF && isalpha(c) ) { *first++ = c; } return static_cast<streamsize>(first - s); } };
OutputFilters can also be Multi-Character. For example:
#include <ctype.h> // toupper #include <boost/iostreams/concepts.hpp> // multichar_output_filter using namespace std; using namespace boost::io; struct toupper_filter : public multichar_output_filter { template<typename Sink> void write(Sink& snk, const char* s, streamsize n) { while (n-- > 0) boost::iostreams::put(snk, toupper(*s++)); } };
[1]This is an instance of a limitation of C++ known as the forwarding problem (see [Dimov]).
[2]Technically, boost::iostreams::get and boost::iostreams::read require that a Source be indirect.
[3]Technically, boost::iostreams::put requires that a Sink be indirect.
Revised 20 May, 2004
© Copyright Jonathan Turkanis, 2004
Use, modification, and distribution are subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)