The C++ Standard Library
I/O Streams
Class Hierarchy
The I/O streams consist mostly of two class hierarchies, the streambuf tree and the ios tree. These two groups of classes work together to provide I/O services to your application.
The Stream Buffer Classes
The stream buffer classes provide the low-level interface with the medium. They read data from the disk, a socket or whatever and store it in a buffer for the use of the programmer. Alternatively they store the data from the programmer in a buffer until it is full, then they write it in one burst to the medium.
All stream buffers derive from the basic_streambuf template. The SL includes subclasses for writing to and from files and memory locations (strings). You can derive your own stream buffer for your own media.

basic_streambuf is an abstract class that offers a set of virtual functions to override. These control the behaviour of the new buffer.
TODO: Write a guide to creating new streambuffers.
The Formatting Stream Classes
On top of the stream buffers sit the formatters. They provide the convenient << interface to output and >> to input. They throw exceptions if you ask them to. They convert numbers and other data types to printable strings and back. They format the data. They are what you usually work with when doing I/O.
All formatting classes derive from the template basic_ios, which in turn derives from the normal class ios_base.

Of course, every basic_name template has a narrow version called name and a wide version called wname.
ios_base contains the enumeration types for the various control flags as well as data members that hold these flags. It also contains a single locale object that is responsible for data formatting and character conversion.
basic_ios contains the character type-dependent information. This includes the fill character (when the output is not as wide as the field width you specified), the character traits (rules how to handle characters) and most importantly a pointer to the stream buffer. Finally it contains a pointer to another stream, the tied stream. Each input stream can be tied to an output stream, which makes them coordinate their operations. The input stream will flush the output stream before attempting read operations. cout and cin are tied together. The specializations are ios for char and wios for wchar_t.
basic_istream contains functions for reading data and obtaining information about the reads. The specializations are istream for char and wistream for wchar_t.
basic_ostream contains functions for writing data and obtaining information about the writes. The specializations are ostream for char and wostream for wchar_t.
basic_iostream inherits the features of both basic_istream and basic_ostream, thus creating a stream that can be both read from and written to. The specializations are iostream for char and wiostream for wchar_t.
basic_ifstream extends basic_istream with a constructor that takes a filename and member functions for opening and closing files. basic_ofstream and basic_fstream do the same for basic_ostream and basic_iostream, respectively. They all create their own file stream buffers. The specializations are ifstream, ofstream and fstream for char and wifstream, wofstream and wfstream for wchar_t.
basic_istringstream works on in-memory string objects via string stream buffers. It extends basic_istream with a constructor that takes an initial string as argument and a member function to retrieve the stored string. basic_ostringstream and basic_stringstream do the same for basic_ostream and basic_iostream, respectively. The specializations are istringstream, ostringstream and stringstream for char and wistringstream, wostringstream and wstringstream for wchar_t.
Character Traits
I've mentioned character traits in the basic_ios description. If you look in a reference you'll find that basic_ios (and all the other stream classes) are templated not only on a character type, but also on a traits type, which defaults to char_traits<chartype>. What is it?
The interesting thing about templates is that they allow an incredible amount of freedom. Since the stream classes are templated on the character type you can pass char or wchar_t, but you could also, for example, pass float as the character type. It is very unlikely that you will be able to rationalize this decision, but it is, in theory, possible. You could also pass a type you defined yourself, a class that represents a special character for example. But whatever you pass, the stream needs to be able to work with it, and for that it needs quite a few operations to perform on the character type.
The character traits template parameter is a struct or class that tells the stream how to perform these operations. It does so by defining a few static functions that actually do these things so that the stream only has to call these functions. It also defines a few type aliases, such as the character type itself, an integer type that can hold the character type or an EOF indicator and some more.
You can therefore define your own character traits if you don't want the functionality that the library's default traits type, char_traits, provides.
Stream Operators
The coolest feature about the streams is that they allow this intuitive syntax of writing and reading data.
> i; cout << "Thanks." << endl;]]>
This is made possible by overloading the bit shift operators so that they become stream operators.
A stream operator is an overload of << or >> that takes a reference to an ostream or istream as first parameter, respectively. The second parameter can be anything you want, which allows you to define stream operators for your own types.
using namespace std;
// This struct stores a point.
struct point
{
int x, y;
};
// This operator allows to insert points into streams.
ostream &operator <<(ostream &os, const point &pt)
{
return os << '(' << pt.x << ',' << pt.y << ')';
}
// This operator allows extraction from a stream.
// Notice that the second argument is non-const.
istream &operator >>(istream &is, point &pt)
{
return ((is.ignore() >> pt.x).ignore() >> pt.y).ignore();
}
// Example
int main()
{
point pt;
cout << "Enter a point: "
cin >> pt;
cout << pt;
}]]>
Enter a point: (23,15) (23,15)
The output operator is easy enough to understand. Each stream operator must return a reference to a stream (usually the one that was passed) to allow the operator to be chained. Therefore we can use the result of the output chain as the result for the operator itself.
The input operator is obfuscated (made unreadable). This is intentional. I want to show that not only the stream operators but also most member functions return the stream they work on, thus allowing even more complicated chains like in this example. More readable code that does exactly the same would be:
> pt.x; is.ignore(); is >> pt.y; is.ignore(), return is;]]>
ignore is a member that reads but doesn't store the number of characters that is passed in the argument. If none is passed it defaults to a single character. We want to ignore the parentheses and comma that belong to the point.
This should be improved. In an effort to filter out invalid data we should actually check that it is really the characters we expect that we ignore and set the stream into an error state otherwise.
>(istream &is, point &pt)
{
char c;
is >> c;
if(c != '(') {
is.setstate(ios_base::failbit);
return is;
}
is >> pt.x;
is >> c;
if(c != ',') {
is.setstate(ios_base::failbit);
return is;
}
is >> pt.y;
is >> c;
if(c != ')') {
is.setstate(ios_base::failbit);
return is;
}
return is;
}]]>
Much longer now, but it will check the input. It could be shortened by reading the three characters and two numbers all at once, but it is better to check each character for validity immediately, before attempting to read follow-up data.
There's still something wrong. All the time I'm going on about templates and character types, but where are they? These functions are hard-coded for char and char_traits<char>, but this is not necessary. Here's the full code, written for character type independence.
using namespace std;
struct point
{
int x, y;
};
template
basic_ostream &operator <<(basic_ostream &os, const point &pt)
{
return os << os.widen('(') << pt.x << os.widen(',')
<< pt.y << os.widen(')');
}
template
basic_istream &operator >>(basic_istream &is, point &pt)
{
C c;
is >> c;
if(!T::eq(c, is.widen('('))) {
is.setstate(ios_base::failbit);
return is;
}
is >> pt.x;
is >> c;
if(!T::eq(c, is.widen(','))) {
is.setstate(ios_base::failbit);
return is;
}
is >> pt.y;
is >> c;
if(!T::eq(c, is.widen(')'))) {
is.setstate(ios_base::failbit);
return is;
}
return is;
}]]>
The usage side hasn't changed, but now it will work with wcin and wcout, too.
Let's remember: the first template parameter, C, is the character type. The second, T, defines the character traits. You never have to supply either, they are deduced from the type of the stream passed to the operator.
What is remarkable about the change? First, the widen method of the streams. widen takes a single byte and returns its representation using the character type. This call is actually forwarded to the character traits, which also have a widen function. widen only works on single characters.
Second, the use of C in the input operator. We want to store a single character, the type for this character is C.
Third, the eq function. eq is a function of the character traits, it compares two characters for equality. We cannot and must not rely on the equality operator here.
That's it. You see, writing your own stream operators is easy.
Stream Manipulators
There are three special stream operators for each class which insert function pointers into the stream. When these operators are called, they execute the function, passing the stream as an argument.
The functions inserted need to have one of three signatures:
basic_ios& function(basic_ios &); template basic_istream & function(basic_istream &); // Or basic_ostream if it's for output streams. ]]>
Specializations of the templates are valid, like in my example, but restrict the manipulator to this specialization.
The three signatures are the reason why there are three stream operators: one for each signature.
Such functions do not often generate output, instead they manipulate the stream's state, which is why they are called manipulators. Surely the most popular manipulator is endl, which is usually implemented like this:
basic_ostream&endl(basic_ostream &) { // Insert a newline. os.put(os.widen('\n')); // Flush the stream. os.flush(); }]]>
Upon insertion, endl will first output a newline (converted to the appropriate character set) and then force a buffer flush.
That's the easy version. There's another sort of manipulators: the kind that takes arguments. setw is such a manipulator. You can define your own, but not in a portable way: it is up to the standard library implementation how to realize parametrized manipulators.
Stream Iterators
The concept of iterators is realized for streams. See Iterators II.
Very well, we can now read and write data. Now we need to store data.