The I/O stream classes rely on stream buffers for the low-level input and output. Most programmers ignore the stream buffer and deal only with high-level streams. You might find yourself dealing with stream buffers from the client side—for example, using stream buffers to perform low-level I/O on entire streams at once—or you might find yourself on the other side, implementing a custom stream buffer. This section discusses both of these aspects.
You can copy a file in several ways. Example 9-3 shows how a C programmer might copy a stream once he has learned about templates.
After measuring the performance of this solution, the intrepid programmer might decide that copying larger buffers is the right way to go. Example 9-4 shows the new approach. On my system, the new version runs roughly twice as fast as the original version. (Of course, performance measures depend highly on compiler, library, environment, and so on.)
Example 9-4. Copying streams with explicit buffers
template<typename charT, typename traits> void copy(std::basic_ostream<charT, traits>& out, std::basic_istream<charT, traits>& in) { const unsigned BUFFER_SIZE = 8192; std::auto_ptr<charT> buffer(new charT[BUFFER_SIZE]); while (in) { in.read(buffer.get( ), BUFFER_SIZE); out.write(buffer.get( ), in.gcount( )); } }
After reading more about the C++ standard library, the programmer might try to improve performance by delegating all the work to the stream buffer, as shown in Example 9-5.
The version in Example 9-5 runs about as fast as the version in Example 9-4 but is much simpler to read and write.
Another reason to mess around with stream buffers is that you
might need to write your own. Perhaps you are implementing a network I/O
package. The user opens a network stream that connects to a particular
port on a particular host and then performs I/O using the normal I/O
streams. To implement your package, you must derive your own stream
buffer class template (basic_networkbuf
) from the basic_streambuf
class template in <streambuf>
.
A stream buffer is characterized by three pointers that
point to the actual buffer, which is a character array. The pointers
point to the beginning of the buffer, the current I/O position (that is,
the next character to read or the next position for writing), and the
end of the buffer. The stream buffer class manages the array and the
pointers. When the array empties upon input, the stream buffer must
obtain additional input from the network (the underflow
function). When the array fills upon
output, the stream buffer must write the data to the network (the
overflow
function). Other functions
include putting back a character after reading it, seeking, and so on.
(See <streambuf>
in Chapter 13 for details about each member
function.) Example 9-6 shows
an extremely oversimplified sketch of how the basic_networkbuf
class template might
work.
Example 9-6. The basic_networkbuf class template
template<typename charT, typename traits = std::char_traits<char> > class basic_networkbuf : public std::basic_streambuf<charT, traits> { public: typedef charT char_type; typedef traits traits_type; typedef typename traits::int_type int_type; typedef typename traits::pos_type pos_type; typedef typename traits::off_type off_type; basic_networkbuf( ); virtual ~basic_networkbuf( ); bool is_connected( ); basic_networkbuf* connect(const char* hostname, int port, std::ios_base::openmode mode); basic_networkbuf* disconnect( ); protected: virtual std::streamsize showmanyc( ); virtual int_type underflow( ); virtual int_type overflow(int_type c = traits::eof( )); virtual pos_type seekoff(off_type offset, std::ios_base::seekdir dir, std::ios_base::openmode); virtual pos_type seekpos(pos_type sp, std::ios_base::openmode); virtual basic_networkbuf* setbuf(char_type* buf, std::streamsize size); virtual int sync( ); private: char_type* buffer_; std::streamsize size_; bool ownbuf_; // true means destructor must delete buffer_ // network connectivity stuff... }; // Construct initializes the buffer pointers. template<typename charT, typename traits> basic_networkbuf<charT,traits>::basic_networkbuf( ) : buffer_(new char_type[DEFAULT_BUFSIZ]), size_(DEFAULT_BUFSIZ), ownbuf_(true) { this->setg(buffer_, buffer_ + size_, buffer_ + size_); // Leave room in the output buffer for one last character. this->setp(buffer_, buffer_ + size_ - 1); } // Return the number of characters available in the input buffer. template<typename charT, typename traits> std::streamsize basic_networkbuf<charT,traits>::showmanyc( ) { return this->egptr( ) - this->gptr( ); } // Fill the input buffer and set up the pointers. template<typename charT, typename traits> typename basic_networkbuf<charT,traits>::int_type basic_networkbuf<charT,traits>::underflow( ) { // Get up to size_ characters from the network, storing them in buffer_. Store // the actual number of characters read in the local variable, count. std::streamsize count; count = netread(buffer_, size_); this->setg(buffer_, buffer_, buffer_ + coun)); if (this->egptr( ) == this->gptr( )) return traits::eof( ); else return traits::to_int_type(*this->gptr( )); } // The output buffer always has room for one more character, so if c is not // eof( ), add it to the output buffer. Then write the buffer to the network // connection. template<typename charT, typename traits> typename basic_networkbuf<charT,traits>::int_type { if (c != traits::eof( )) { *(this->pptr( )) = c; this->pbump(1); } netwrite(this->pbase(), this->pptr( ) - this->pbase( )); // The output buffer is now empty. Make sure it has room for one last // character. this->setp(buffer_, buffer_ + size_ - 1); return traits::not_eof(c); } // Force a buffer write. template<typename charT, typename traits> int basic_networkbuf<charT,traits>::sync( ) { overflow(traits::eof( )); return 0; }