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. |