Tutorial

2.1.3. Writing a container_sink

Suppose you want to write a Device for appending characters to an STL container. A Device which only supports writing is called a Sink. A typical narrow-character Sink looks like this:

#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 */
};

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 sink_tag indicates that only write is supported.

The member function write writes up to n character into the buffer s and returns the number of character written. In general, write may return fewer characters than requested, in which case the Sink is call non-blocking. Non-blocking Devices do not interact well with stanard streams and stream buffers, however, so most devices should be Blocking. See Asynchronous and Non-Blocking I/O.

You could also write the above example as follows:

#include <boost/iostreams/concepts.hpp>  // sink

class my_sink : public sink {
public:
    std::streamsize write(const char* s, std::streamsize n);

    /* Other members */
};

Here sink 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_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

Here, note that

You can write to a container_sink as follows

#include <cassert>
#include <string>
#include <boost/iostreams/stream.hpp>
#include <libs/iostreams/example/container_device.hpp> // container_sink

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!");
}

Note that the Iostreams library provides buffering by default. Consequently, the stream out must be flushed before the characters written are guaranteed to be reflected in the underlying string.

Finally, I should mention that the Iostreams library offers easier ways to append to an STL-compatible container. First, OutputIterators can be added directly to filtering streams and stream buffers. So you could write:

#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!");
}

Second, the Iostreams library provides a version of back_inserter that is somewhat more efficient than std::back_inserter because the Sink it returns uses insert rather than push_back. So you could write:

#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!");
}