Apache C++ Standard Library User's Guide

## 39.2 Deriving New Stream Buffer Classes

Deriving a new stream buffer class is not commonly necessary, but it can be extraordinarily useful when specialized behavior is needed. For example, consider a log book stream that starts writing at the beginning after reaching a certain size, so that the log file does not grow infinitely. In order to implement this new class, we first need to derive a new stream buffer type. The easiest way to do this is to derive from basic_filebuf, and then reimplement one of the protected virtual functions:

    #include <fstream>

    template <class charT, class traits = std::char_traits<charT> >
    class logbuffer: public std::basic_filebuf<charT, traits>
    {
        std::streamsize max_size;
        std::streamsize cur_size;

    public:
        typedef charT                          char_type;      //1
        typedef traits                         traits_type;
        typedef typename traits_type::int_type int_type;
        typedef typename traits_type::off_type off_type;
        typedef typename traits_type::pos_type pos_type;

        logbuffer(std::streamsize sz)                          //2
            : max_size (sz), cur_size (0) { }

    protected:
        int_type overflow (int_type c = traits_type::eof ());  //3
    };

`//1` | All derived streams should have these types defined, even though they are inherited from the base, to make referring to them easy.
`//2` | This constructor takes a size parameter that we'll use to limit the size of the log file.
`//3` | The protected virtual function `overflow(int_type)` is called whenever a stream attempts to write to the stream buffer when the put area is full. We re-implement the function in order to reset the file pointer to the beginning whenever the file gets too large.

The `overflow()` member function is implemented as follows:

    template <class charT, class traits>
    typename logbuffer<charT, traits>::int_type
    logbuffer<charT, traits>::
    overflow (typename logbuffer<charT, traits>::int_type c)
    {
        if (cur_size > max_size)
            return traits_type::not_eof (c);                     //1

        std::streamsize len = this->pptr () - this->pbase ();    //2
        std::streamsize rem = cur_size + len - max_size;         //3

        std::basic_string<charT, traits> saved;

        if (rem > 0) {                                           //4
            this->pbump (-rem);

            saved.assign (this->pptr (), rem);
            if (!traits_type::eq_int_type (c, traits_type::eof ()))
                saved += c;

            len = max_size - cur_size;
            c = traits_type::eof ();
        }

        const int_type ret = std::basic_filebuf<charT,
          traits>::overflow (c);                                 //5

        cur_size += len;                                         //5

        if (cur_size == max_size) {                              //6
            cur_size = max_size + 1;
            this->pubseekoff (0, std::ios_base::beg);
            cur_size = 0;
        }

        if (saved.size ())
            this->xsputn (saved.data (), saved.size ());         //7

        return ret;
    }

`//1` | The conditional expression `cur_size > max_size` guards against infinite recursion that might occur from `overflow()` being called recursively from some other method, such as `pubseekoff()` in code block `//6` below.
`//2` | Compute the size `len` of the pending output sequence.
`//3` | Determine whether the pending output sequence will exceed `max_size`, the maximum size of the file. If the computed value of `rem` is greater than zero, `max_size` has been reached, and `rem` represents the number of characters which, if output, would exceed the maximum size of the file.

`//4`

If `rem` is greater than zero, temporarily remove `rem` characters from the end of the pending output sequence so that when the sequence is output the total length output so far will be equivalent to `max_size`. Save the characters removed from the pending output sequence in `saved`. Also, reset the size of `len` to correctly represent the number of characters remaining in the pending output sequence.

`//5` | Call the overridden function `overflow()` to write out the pending output sequence, which is now guaranteed not to make the total number of characters output exceed `max_size`. Then adjust `cur_size` to reflect the total number of characters output so far.

`//6`

If number of characters output so far is equal to `max_size`, a condition usually created in code block `//4` above when an overflow condition is detected, temporarily invalidate `cur_size` by setting it to a value greater than its valid maximum value to prevent infinite recursion. Then seek to the beginning of the output log so that any future output will start overwriting it from the beginning.

`//7` | If `saved` contains data because of an overflow condition, write the overflow data to the pending output sequence.

In order to use this new logbuf class template, we do not necessarily need a new logstream class template. But it might come in handy in some cases so we provide a definition for one just in case:

    template <class charT, class traits = std::char_traits<charT> >
    class logstream: public std::basic_iostream<charT, traits>
    {
        logbuffer<charT, traits> buf;                        //1

    public:
        typedef charT                          char_type;    //2
        typedef traits                         traits_type;
        typedef typename traits_type::int_type int_type;
        typedef typename traits_type::off_type off_type;
        typedef typename traits_type::pos_type pos_type;

        logstream(std::streamsize, const char*);             //3
        logbuffer<charT, traits> *rdbuf () const {           //4
            return (logbuffer<charT, traits>*)&buf;
        }
    };

`//1` | This private member provides us with a logbuffer object.
`//2` | Again, we want to define the standard set of types.
`//3` | This constructor creates a stream with the given maximum size and the given file name.
`//4` | The `const` member function `rdbuf()` returns a non-`const` pointer to the `logbuffer` data member.

Finally, we implement our new log stream class as shown here:

    template <class charT, class traits>
    logstream<charT, traits>::logstream (std::streamsize sz,
      const char* file)
        : std::basic_iostream<charT, traits>(&buf), buf (sz)
    {
        this->init (&buf);                                //1
        if (!buf.open (file, std::ios_base::out))         //2
            this->setstate (std::ios_base::failbit);
    }

`//1` | The `ios_base::init()` member function initializes the base class. In part the initialization consists of installing a pointer to the logbuffer in the ios_base so base classes have access to the buffer.
`//2` | Open the file for writing.

We can use this new log buffer class as follows:

    int main ()
    {
        logstream<char> log (256, "test.log");         //1

        char buf [33];                                 //2
        log.rdbuf ()->pubsetbuf (buf, sizeof buf);     //2

        for (int i = 0; i < 150; i++) {                //3
            log << '[' << i << ']';
        }
    }

`//1` | Construct a logstream object, setting the maximum size of its output sequence (that is, the maximum size of the `test.log` file) to 256 bytes.
`//2` | Create a character array `buf` of 33 characters, and set the internal character buffer maintained by the log's buffer to `buf`.

`//3`

Write the integers `0` through `149` enclosed in brackets to the log stream object.
The total output exceeds 256 characters, yet the file created by the example would not exceed 256 characters. If you opened the file, you would see:
`24][125][126][127][128][129][130][131][132][133][134] [135][136][137][138][139][140][141][142][143][144][14 5][146][147][148][149]8][99][100][101][102][103][104] [105][106][107][108][109][110][111][112][113][114][11 5][116][117][118][119][120][121][122][123][1`
Note that the last complete number at the end of the file is `123`, and that the next number, `124`, is split between the end of the file (`[1`) and the beginning of the file (`24]`). This demonstrates that the output wrapped from the end of the file back to the beginning, overwriting the earlier output. The last number output, `149`, overwrote the output for number `98`, leaving only the last digit.