Iostreams
There’s much more you can do with the general I/O problem
than just take standard I/O and turn it into a class.
Wouldn’t it be nice if you could make all the usual “receptacles” – standard I/O, files and
even blocks of memory – look the same, so you need to remember only one interface? That’s
the idea behind iostreams. They’re much easier, safer, and often more efficient than the
assorted functions from the Standard C stdio library.
Iostream is usually the first class library that new C++ programmers learn to use. This chapter
explores the use of iostreams, so they can replace the C I/O functions through the rest of the
book. In future chapters, you’ll see how to set up your own classes so they’re compatible with
iostreams.
Why iostreams?
You may wonder what’s wrong with the good old C library. And why not “wrap” the C
library in a class and be done with it? Indeed, there are situations when this is the perfect thing
to do, when you want to make a C library a bit safer and easier to use. For example, suppose
you want to make sure a stdio file is always safely opened and properly closed, without
relying on the user to remember to call the close( ) function:
//: C02:FileClass.h
// Stdio files wrapped
#ifndef FILECLAS_H
#define FILECLAS_H
#include <cstdio>
class FileClass {
std::FILE* f;
public:
FileClass(const char* fname, const char* mode="r");
~FileClass();
std::FILE* fp();
};
#endif // FILECLAS_H ///:~
Chapter 14: Templates & Container Classes
64
In C when you perform file I/O, you work with a naked pointer to a FILE struct, but this class
wraps around the pointer and guarantees it is properly initialized and cleaned up using the
constructor and destructor. The second constructor argument is the file mode, which defaults
to “r” for “read.”
To fetch the value of the pointer to use in the file I/O functions, you use the fp( ) access
function. Here are the member function definitions:
//: C02:FileClass.cpp {O}
// Implementation
#include "FileClass.h"
#include <cstdlib>
using namespace std;
FileClass::FileClass(const char* fname, const char* mode){
f = fopen(fname, mode);
if(f == NULL) {
printf("%s: file not found\n", fname);
exit(1);
}
}
FileClass::~FileClass() { fclose(f); }
FILE* FileClass::fp() { return f; } ///:~
The constructor calls fopen( ),as you would normally do, but it also checks to ensure the
result isn’t zero, which indicates a failure upon opening the file. If there’s a failure, the name
of the file is printed and exit( ) is called.
The destructor closes the file, and the access function fp( )returns f. Here’s a simple example
using class FileClass:
//: C02:FileClassTest.cpp
//{L} FileClass
// Testing class File
#include "FileClass.h"
#include "../require.h"
using namespace std;
int main(int argc, char* argv[]) {
requireArgs(argc, 1);
FileClass f(argv[1]); // Opens and tests
const int bsize = 100;
char buf[bsize];
while(fgets(buf, bsize, f.fp()))
Chapter 14: Templates & Container Classes
65
puts(buf);
} // File automatically closed by destructor
///:~
You create the FileClass object and use it in normal C file I/O function calls by calling fp( ).
When you’re done with it, just forget about it, and the file is closed by the destructor at the
end of the scope.
True wrapping
Even though the FILE pointer is private, it isn’t particularly safe because fp( ) retrieves it. The
only effect seems to be guaranteed initialization and cleanup, so why not make it public, or
use a struct instead? Notice that while you can get a copy of f using fp( ), you cannot assign
to f – that’s completely under the control of the class. Of course, after capturing the pointer
returned by fp( ), the client programmer can still assign to the structure elements, so the safety
is in guaranteeing a valid FILE pointer rather than proper contents of the structure.
If you want complete safety, you have to prevent the user from direct access to the FILE
pointer. This means some version of all the normal file I/O functions will have to show up as
class members, so everything you can do with the C approach is available in the C++ class:
//: C02:Fullwrap.h
// Completely hidden file IO
#ifndef FULLWRAP_H
#define FULLWRAP_H
class File {
std::FILE* f;
std::FILE* F(); // Produces checked pointer to f
public:
File(); // Create object but don't open file
File(const char* path,
const char* mode = "r");
~File();
int open(const char* path,
const char* mode = "r");
int reopen(const char* path,
const char* mode);
int getc();
int ungetc(int c);
int putc(int c);
int puts(const char* s);
char* gets(char* s, int n);
int printf(const char* format, ...);
size_t read(void* ptr, size_t size,
Chapter 14: Templates & Container Classes
66
size_t n);
size_t write(const void* ptr,
size_t size, size_t n);
int eof();
int close();
int flush();
int seek(long offset, int whence);
int getpos(fpos_t* pos);
int setpos(const fpos_t* pos);
long tell();
void rewind();
void setbuf(char* buf);
int setvbuf(char* buf, int type, size_t sz);
int error();
void clearErr();
};
#endif // FULLWRAP_H ///:~
This class contains almost all the file I/O functions from cstdio. vfprintf( ) is missing; it is
used to implement the printf( ) member function.
File has the same constructor as in the previous example, and it also has a default constructor.
The default constructor is important if you want to create an array of File objects or use a File
object as a member of another class where the initialization doesn’t happen in the constructor
(but sometime after the enclosing object is created).
The default constructor sets the private FILE pointer f to zero. But now, before any reference
to f, its value must be checked to ensure it isn’t zero. This is accomplished with the last
member function in the class, F( ), which is private because it is intended to be used only by
other member functions. (We don’t want to give the user direct access to the FILE structure
in this class.)
This is not a terrible solution by any means. It’s quite functional, and you could imagine
making similar classes for standard (console) I/O and for in-core formatting (reading/writing a
piece of memory rather than a file or the console).
The big stumbling block is the runtime interpreter used for the variable-argument list
functions. This is the code that parses through your format string at runtime and grabs and
interprets arguments from the variable argument list. It’s a problem for four reasons.
1. Even if you use only a fraction of the functionality of the interpreter, the
whole thing gets loaded. So if you say:
The implementation and test files for FULLWRAP are available in the freely distributed
source code for this book. See preface for details.
printf("%c", 'x');
you’ll get the whole package, including the parts that print out floatingpoint
numbers and strings. There’s no option for reducing the amount of
space used by the program.
2. Because the interpretation happens at runtime there’s a performance
overhead you can’t get rid of. It’s frustrating because all the information is
there in the format string at compile time, but it’s not evaluated until
runtime. However, if you could parse the arguments in the format string at
compile time you could make hard function calls that have the potential to
be much faster than a runtime interpreter (although the printf( ) family of
functions is usually quite well optimized).
3. A worse problem occurs because the evaluation of the format string doesn’t
happen until runtime: there can be no compile-time error checking. You’re
probably very familiar with this problem if you’ve tried to find bugs that
came from using the wrong number or type of arguments in a printf( )
statement. C++ makes a big deal out of compile-time error checking to find
errors early and make your life easier. It seems a shame to throw it away for
an I/O library, especially because I/O is used a lot.
4. For C++, the most important problem is that the printf( ) family of
functions is not particularly extensible. They’re really designed to handle
the four basic data types in C (char, int, float, double and their variations).
You might think that every time you add a new class, you could add an
overloaded printf( ) and scanf( ) function (and their variants for files and
strings) but remember, overloaded functions must have different types in
their argument lists and the printf( ) family hides its type information in the
format string and in the variable argument list. For a language like C++,
whose goal is to be able to easily add new data types, this is an ungainly
restriction.
Iostreams to the rescue
All these issues make it clear that one of the first standard class libraries for C++ should
handle I/O. Because “hello, world” is the first program just about everyone writes in a new
language, and because I/O is part of virtually every program, the I/O library in C++ must be
particularly easy to use. It also has the much greater challenge that it can never know all the
classes it must accommodate, but it must nevertheless be adaptable to use any new class. Thus its constraints required that this first class be a truly inspired design.
This chapter won’t look at the details of the design and how to add iostream functionality to
your own classes (you’ll learn that in a later chapter). First, you need to learn to use iostreams.
In addition to gaining a great deal of leverage and clarity in your dealings with I/O and
formatting, you’ll also see how a really powerful C++ library can work.
Sneak preview of operator overloading
Before you can use the iostreams library, you must understand one new feature of the
language that won’t be covered in detail until a later chapter. To use iostreams, you need to
know that in C++ all the operators can take on different meanings. In this chapter, we’re
particularly interested in << and >>. The statement “operators can take on different
meanings” deserves some extra insight.
In Chapter XX, you learned how function overloading allows you to use the same function
name with different argument lists. Now imagine that when the compiler sees an expression
consisting of an argument followed by an operator followed by an argument, it simply calls a
function. That is, an operator is simply a function call with a different syntax.
Of course, this is C++, which is very particular about data types. So there must be a
previously declared function to match that operator and those particular argument types, or
the compiler will not accept the expression.
What most people find immediately disturbing about operator overloading is the thought that
maybe everything they know about operators in C is suddenly wrong. This is absolutely false.
Here are two of the sacred design goals of C++:
1. A program that compiles in C will compile in C++. The only compilation
errors and warnings from the C++ compiler will result from the “holes” in
the C language, and fixing these will require only local editing. (Indeed, the
complaints by the C++ compiler usually lead you directly to undiscovered
bugs in the C program.)
2. The C++ compiler will not secretly change the behavior of a C program by
recompiling it under C++.
Keeping these goals in mind will help answer a lot of questions; knowing there are no
capricious changes to C when moving to C++ helps make the transition easy. In particular,
operators for built-in types won’t suddenly start working differently – you cannot change their
meaning. Overloaded operators can be created only where new data types are involved. So
you can create a new overloaded operator for a new class, but the expression
1 << 4;
won’t suddenly change its meaning, and the illegal code
1.414 << 1;
won’t suddenly start working.
Inserters and extractors
In the iostreams library, two operators have been overloaded to make the use of iostreams
easy. The operator << is often referred to as an inserter for iostreams, and the operator >> is
often referred to as an extractor.
A stream is an object that formats and holds bytes. You can have an input stream (istream) or
an output stream (ostream). There are different types of istreams and ostreams: ifstreams and
ofstreams for files, istrstreams , and ostrstreams for char* memory (in-core formatting), and
istringstreams & ostringstreams for interfacing with the Standard C++ string class. All these
stream objects have the same interface, regardless of whether you’re working with a file,
standard I/O, a piece of memory or a string object. The single interface you learn also works
for extensions added to support new classes.
If a stream is capable of producing bytes (an istream), you can get information from the
stream using an extractor. The extractor produces and formats the type of information that’s
expected by the destination object. To see an example of this, you can use the cin object,
which is the iostream equivalent of stdin in C, that is, redirectable standard input. This object
is pre-defined whenever you include the iostream.h header file. (Thus, the iostream library is
automatically linked with most compilers.)
int i;
cin >> i;
float f;
cin >> f;
char c;
cin >> c;
char buf[100];
cin >> buf;
There’s an overloaded operator >> for every data type you can use as the right-hand
argument of >> in an iostream statement. (You can also overload your own, which you’ll see
in a later chapter.)
To find out what you have in the various variables, you can use the cout object
(corresponding to standard output; there’s also a cerr object corresponding to standard error)
with the inserter <<:
cout << "i = ";
cout << i;
cout << "\n";
cout << "f = ";
cout << f;
cout << "\n";
cout << "c = ";
cout << c;
cout << "\n";
cout << "buf = ";
cout << buf;
cout << "\n";
This is notably tedious, and doesn’t seem like much of an improvement over printf( ), type
checking or no. Fortunately, the overloaded inserters and extractors in iostreams are designed
to be chained together into a complex expression that is much easier to write:
cout << "i = " << i << endl;
cout << "f = " << f << endl;
cout << "c = " << c << endl;
cout << "buf = " << buf << endl;
You’ll understand how this can happen in a later chapter, but for now it’s sufficient to take the
attitude of a class user and just know it works that way.
Manipulators
One new element has been added here: a manipulator called endl. A manipulator acts on the
stream itself; in this case it inserts a newline and flushes the stream (puts out all pending
characters that have been stored in the internal stream buffer but not yet output). You can also
just flush the stream:
cout << flush;
There are additional basic manipulators that will change the number base to oct (octal), dec
(decimal) or hex (hexadecimal):
cout << hex << "0x" << i << endl;
There’s a manipulator for extraction that “eats” white space:
cin >> ws;
and a manipulator called ends, which is like endl, only for strstreams (covered in a while).
These are all the manipulators in <iostream>, but there are more in <iomanip> you’ll see
later in the chapter.
Common usage
Although cin and the extractor >> provide a nice balance to cout and the inserter <<, in
practice using formatted input routines, especially with standard input, has the same problems
you run into with scanf( ). If the input produces an unexpected value, the process is skewed,
and it’s very difficult to recover. In addition, formatted input defaults to whitespace
delimiters. So if you collect the above code fragments into a program
//: C02:Iosexamp.cpp
// Iostream examples
#include <iostream>
using namespace std;
int main() {
int i;
cin >> i;
float f;
cin >> f;
char c;
cin >> c;
char buf[100];
cin >> buf;
cout << "i = " << i << endl;
cout << "f = " << f << endl;
cout << "c = " << c << endl;
cout << "buf = " << buf << endl;
cout << flush;
cout << hex << "0x" << i << endl;
} ///:~
and give it the following input,
12 1.4 c this is a test
you’ll get the same output as if you give it
12
1.4
c
this is a test
and the output is, somewhat unexpectedly,
i = 12
f = 1.4
c = c
bu
Notice that buf got only the first word because the input routine looked for a space to delimit
the input, which it saw after “this.” In addition, if the continuous input string is longer than
the storage allocated for buf, you’ll overrun the buffer.
It seems cin and the extractor are provided only for completeness, and this is probably a good
way to look at it. In practice, you’ll usually want to get your input a line at a time as a
sequence of characters and then scan them and perform conversions once they’re safely in a
buffer. This way you don’t have to worry about the input routine choking on unexpected data.
Another thing to consider is the whole concept of a command-line interface. This has made
sense in the past when the console was little more than a glass typewriter, but the world is
rapidly changing to one where the graphical user interface (GUI) dominates. What is the
meaning of console I/O in such a world? It makes much more sense to ignore cin altogether
other than for very simple examples or tests, and take the following approaches:
1. If your program requires input, read that input from a file – you’ll soon see
it’s remarkably easy to use files with iostreams. Iostreams for files still
works fine with a GUI.
2. Read the input without attempting to convert it. Once the input is someplace
where it can’t foul things up during conversion, then you can safely scan it.
3. Output is different. If you’re using a GUI, cout doesn’t work and you must
send it to a file (which is identical to sending it to cout) or use the GUI
facilities for data display. Otherwise it often makes sense to send it to cout.
In both cases, the output formatting functions of iostreams are highly useful.
Line-oriented input
To grab input a line at a time, you have two choices: the member functions get( ) and
getline( ). Both functions take three arguments: a pointer to a character buffer in which to
store the result, the size of that buffer (so they don’t overrun it), and the terminating character,
to know when to stop reading input. The terminating character has a default value of ‘\n’,
which is what you’ll usually use. Both functions store a zero in the result buffer when they
encounter the terminating character in the input.
So what’s the difference? Subtle, but important: get( ) stops when it sees the delimiter in the
input stream, but it doesn’t extract it from the input stream. Thus, if you did another get( )
using the same delimiter it would immediately return with no fetched input. (Presumably, you
either use a different delimiter in the next get( ) statement or a different input function.)
getline( ), on the other hand, extracts the delimiter from the input stream, but still doesn’t
store it in the result buffer.
Generally, when you’re processing a text file that you read a line at a time, you’ll want to use
getline( ).
Overloaded versions of get( )
get( ) also comes in three other overloaded versions: one with no arguments that returns the
next character, using an int return value; one that stuffs a character into its char argument,
using a reference (You’ll have to jump forward to Chapter XX if you want to understand it
right this minute . . . .); and one that stores directly into the underlying buffer structure of
another iostream object. That is explored later in the chapter.
Reading raw bytes
If you know exactly what you’re dealing with and want to move the bytes directly into a
variable, array, or structure in memory, you can use read( ). The first argument is a pointer to
the destination memory, and the second is the number of bytes to read. This is especially
useful if you’ve previously stored the information to a file, for example, in binary form using
the complementary write( ) member function for an output stream. You’ll see examples of all
these functions later.
Error handling
All the versions of get( ) and getline( ) return the input stream from which the characters
came except for get( ) with no arguments, which returns the next character or EOF. If you get
the input stream object back, you can ask it if it’s still OK. In fact, you can ask any iostream
object if it’s OK using the member functions good( ), eof( ), fail( ), and bad( ). These return
state information based on the eofbit (indicates the buffer is at the end of sequence), the
failbit (indicates some operation has failed because of formatting issues or some other
problem that does not affect the buffer) and the badbit (indicates something has gone wrong
with the buffer).
However, as mentioned earlier, the state of an input stream generally gets corrupted in weird
ways only when you’re trying to do input to specific types and the type read from the input is
inconsistent with what is expected. Then of course you have the problem of what to do with
the input stream to correct the problem. If you follow my advice and read input a line at a
time or as a big glob of characters (with read( )) and don’t attempt to use the input formatting
functions except in simple cases, then all you’re concerned with is whether you’re at the end
of the input (EOF). Fortunately, testing for this turns out to be simple and can be done inside
of conditionals, such as while(cin) or if(cin). For now you’ll have to accept that when you use
an input stream object in this context, the right value is safely, correctly and magically
produced to indicate whether the object has reached the end of the input. You can also use the
Boolean NOT operator !, as in if(!cin), to indicate the stream is not OK; that is, you’ve
probably reached the end of input and should quit trying to read the stream.
There are times when the stream becomes not-OK, but you understand this condition and
want to go on using it. For example, if you reach the end of an input file, the eofbit and failbit
are set, so a conditional on that stream object will indicate the stream is no longer good.
However, you may want to continue using the file, by seeking to an earlier position and
reading more data. To correct the condition, simply call the clear( ) member function.
File iostreams
Manipulating files with iostreams is much easier and safer than using cstdio in C. All you do
to open a file is create an object; the constructor does the work. You don’t have to explicitly
close a file (although you can, using the close( ) member function) because the destructor will
close it when the object goes out of scope.
To create a file that defaults to input, make an ifstream object. To create one that defaults to
output, make an ofstream object.
Here’s an example that shows many of the features discussed so far. Note the inclusion of
<fstream> to declare the file I/O classes; this also includes <iostream>.
//: C02:Strfile.cpp
// Stream I/O with files
// The difference between get() & getline()
#include "../require.h"
#include <fstream>
#include <iostream>
using namespace std;
int main() {
const int sz = 100; // Buffer size;
char buf[sz];
{
ifstream in("Strfile.cpp"); // Read
assure(in, "Strfile.cpp"); // Verify open
ofstream out("Strfile.out"); // Write
assure(out, "Strfile.out");
int i = 1; // Line counter
// A less-convenient approach for line input:
while(in.get(buf, sz)) { // Leaves \n in input
in.get(); // Throw away next character (\n)
cout << buf << endl; // Must add \n
// File output just like standard I/O:
7 Newer implementations of iostreams will still support this style of handling errors, but in
some cases will also throw exceptions.
out << i++ << ": " << buf << endl;
}
} // Destructors close in & out
ifstream in("Strfile.out");
assure(in, "Strfile.out");
// More convenient line input:
while(in.getline(buf, sz)) { // Removes \n
char* cp = buf;
while(*cp != ':')
cp++;
cp += 2; // Past ": "
cout << cp << endl; // Must still add \n
}
} ///:~
The creation of both the ifstream and ofstream are followed by an assure( ) to guarantee the
file has been successfully opened. Here again the object, used in a situation where the
compiler expects an integral result, produces a value that indicates success or failure. (To do
this, an automatic type conversion member function is called. These are discussed in Chapter
XX.)
The first while loop demonstrates the use of two forms of the get( ) function. The first gets
characters into a buffer and puts a zero terminator in the buffer when either sz – 1 characters
have been read or the third argument (defaulted to ‘\n’) is encountered. get( ) leaves the
terminator character in the input stream, so this terminator must be thrown away via in.get( )
using the form of get( ) with no argument, which fetches a single byte and returns it as an int.
You can also use the ignore( ) member function, which has two defaulted arguments. The
first is the number of characters to throw away, and defaults to one. The second is the
character at which the ignore( ) function quits (after extracting it) and defaults to EOF.
Next you see two output statements that look very similar: one to cout and one to the file out.
Notice the convenience here; you don’t need to worry about what kind of object you’re
dealing with because the formatting statements work the same with all ostream objects. The
first one echoes the line to standard output, and the second writes the line out to the new file
and includes a line number.
To demonstrate getline( ), it’s interesting to open the file we just created and strip off the line
numbers. To ensure the file is properly closed before opening it to read, you have two choices.
You can surround the first part of the program in braces to force the out object out of scope,
thus calling the destructor and closing the file, which is done here. You can also call close( )
for both files; if you want, you can even reuse the in object by calling the open( ) member
function (you can also create and destroy the object dynamically on the heap as is in Chapter
XX).