# 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

- The member type `char_type` is defined to be equal to the containers's `value_type`;
- The member type `category` tells the Iostreams library that `container_sink` is a model of Sink;
- A `container_sink` can be constructed from a instance of `Container`, which is passed and stored by reference, and accessible via the member function `container()`; and
- The implementation of `write()` simply appends the characters in the specified buffer to the underlying container using the container's `insert` funcion,

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

Revised 02 Feb 2008

© 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)