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 sidefor example, using stream buffers to perform low-level I/O on entire streams at onceor 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.
template<typename charT, typename traits> void copy(std::basic_ostream<charT, traits>& out, std::basic_istream<charT, traits>& in) { charT c; while (in.get(c)) out.put(c); }
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.)
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.
template<typename charT, typename traits> void copy(std::basic_ostream<charT, traits>& out, std::basic_istream<charT, traits>& in) { out << in.rdbuf( ); }
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.
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; }