Deriving a new streambuf 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 filebuf, and then reimplement one of the protected virtual functions:
template <class charT, class traits> class logbuffer : public std::basic_filebuf<charT,traits> { std::streamsize max_size; public: typedef charT char_type; // 1 typedef traits::int_type int_type; typedef traits::off_type off_type; typedef traits::pos_type pos_type; typedef traits traits_type; logbuffer(std::streamsize sz) : max_size(sz) // 2 { ; } protected: int_type overflow(int_type c = traits::eof()); // 3 };
//1 | All streams should have these types defined. |
//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(char_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 function is implemented as follows:
template <class charT, class traits> logbuffer<charT,traits>::int_type logbuffer<charT,traits>::overflow(logbuffer<charT,traits>::int_type c) { using std::ios_base; using std::basic_filebuf; std::streamsize len = epptr() - pbase(); // 1 int_type ret = basic_filebuf<charT,traits>::overflow(c); // 2 if (seekoff(0,ios_base::cur) > max_size - len) // 3 seekoff(0,ios_base::beg); // 4 return ret; // 5 }
//1 | First, retain the size of the put area of the buffer in a local variable. Both epptr() and pbase() are protected functions in basic_streambuf that return, respectively, the beginning and end of the put area. Other protected functions return the beginning and end of the get area, and also the current get and put positions. See the Class Reference for a full description of these functions. |
//2 | Next, call filebuf's ovrflow to write the put area of the buffer out to the file. |
//3 | Now use seekoff to get the current stream position and see if the distance between that and our maximum file size is less than the size of the put buffer. If it is, the next flush of the put area will exceed the maximum size we've set for the file. |
//4 | Seek back to the beginning of the stream(file) when the log is getting too large. Future writes to the stream will overwrite the contents of the log file starting at the beginning. |
//5 | Finally, return the value we got from filebuf's overflow function. |
In order to use this new logbuf class, we'll also need a new logstream class:
template <class charT, class traits> class logstream : public std::basic_iostream<charT,traits> { logbuffer<charT,traits> buf; // 1 public: typedef charT char_type; // 2 typedef traits::int_type int_type; typedef traits::off_type off_type; typedef traits::pos_type pos_type; typedef traits traits_type; logstream(std::streamsize sz, char* file); // 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 | rdbuf returns a pointer to the logbuffer. |
Finally, we implement our new log stream class as shown here:
template <class charT, class traits> logstream<charT,traits>::logstream(std::streamsize sz, char* file) : buf(sz) { using std::ios_base; init(&buf); // 1 if ( !buf.open(file, ios_base::out) ) // 2 setstate(ios_base::failbit); buf.pubseekoff(0,ios_base::beg); // 3 }
//1 | The ios_base::init() 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. |
//3 | Always start out writing at the beginning. |
We can use this new log buffer class as follows:
int main () { using std::char_traits; using std::endl; logstream<char,char_traits<char> > log(4000,"test.log"); // 1 for (int i = 0; i < 1000; i++) // 2 log << i << ' '; log.rdbuf()->pubseekoff(0,std::ios_base::beg); // 3 int in = 0; log >> in; // 4 return 0; }
//1 | Create a logstream object with a maximum size of 4000 characters and attach it to the file test.log. |
//2 | Write out the integers from 0 to 999 to the log file. The total number of characters required to represent these numbers along with the spaces between them will exceed the maximum size we've set. |
//3 | Seek to the beginning of the file. |
//4 | Look at the first value in the file. If we had used an ordinary fstream then this value would be 0, the first value we wrote out. Instead, we used a logstream and wrote out more than the log can hold, so we'll get a different number since the log has wrapped around to the beginning. |
OEM Edition, ©Copyright 1999, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.