Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
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
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.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |