Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Iostream buffering

Good design practice dictates that, whenever you create a new class, you should endeavor to hide the details of the underlying implementation as much as possible from the user of the class. You show them only what they need to know and make the rest private to avoid confusion. When using inserters and extractors, you normally don t know or care where the bytes are being produced or consumed, whether you re dealing with standard I/O, files, memory, or some newly created class or device.

A time comes, however, when it is important to communicate with the part of the iostream that produces and consumes bytes. To provide this part with a common interface and still hide its underlying implementation, the standard library abstracts it into its own class, called streambuf. Each iostream object contains a pointer to some kind of streambuf. (The type depends on whether it deals with standard I/O, files, memory, and so on.) You can access the streambuf directly; for example, you can move raw bytes into and out of the streambuf without formatting them through the enclosing iostream. This is accomplished by calling member functions for the streambuf object.

Currently, the most important thing for you to know is that every iostream object contains a pointer to a streambuf object, and the streambuf object has some member functions you can call if necessary. For file and string streams, there are specialized types of stream buffers, as the following figure illustrates:

To allow you to access the streambuf, every iostream object has a member function called rdbuf( ) that returns the pointer to the object s streambuf. This way you can call any member function for the underlying streambuf. However, one of the most interesting things you can do with the streambuf pointer is to connect it to another iostream object using the << operator. This drains all the characters from your object into the one on the left side of the <<. If you want to move all the characters from one iostream to another, you don t need to go through the tedium (and potential coding errors) of reading them one character or one line at a time. This is a much more elegant approach.

Here s a simple program that opens a file and sends the contents to standard output (similar to the previous example):

//: C04:Stype.cpp
// Type a file to standard output.
#include <fstream>
#include <iostream>
#include "../require.h"
using namespace std;
 
int main() {
ifstream in("Stype.cpp");
assure(in, "Stype.cpp");
cout << in.rdbuf(); // Outputs entire file
} ///:~
 

An ifstream is created using the source code file for this program as an argument. The assure( ) function reports a failure if the file cannot be opened. All the work really happens in the statement

cout << in.rdbuf();
 

which sends the entire contents of the file to cout. This is not only more succinct to code, it is often more efficient than moving the bytes one at a time.

A form of get( ) writes directly into the streambuf of another object. The first argument is a reference to the destination streambuf, and the second is the terminating character ( \n by default), which stops the get( ) function. So there is yet another way to print a file to standard output:

//: C04:Sbufget.cpp
// Copies a file to standard output.
#include <fstream>
#include <iostream>
#include "../require.h"
using namespace std;
 
int main() {
ifstream in("Sbufget.cpp");
assure(in);
streambuf& sb = *cout.rdbuf();
while(!in.get(sb).eof()) {
if(in.fail()) // Found blank line
in.clear();
cout << char(in.get()); // Process '\n'
}
} ///:~
 

The rdbuf( ) function returns a pointer, so it must be dereferenced to satisfy the function s need to see an object. Stream buffers are not meant to be copied (they have no copy constructor), so we define sb as a reference to cout s stream buffer. We need the calls to fail( ) and clear( ) in case the input file has a blank line (this one does). When this particular overloaded version of get( ) sees two newlines in a row (evidence of a blank line), it sets the input stream s fail bit, so we must call clear( ) to reset it so that the stream can continue to be read. The second call to get( ) extracts and echoes each newline delimiter. (Remember, the get( ) function doesn t extract its delimiter like getline( ) does.)

You probably won t need to use a technique like this often, but it s nice to know it exists.[45]

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire