Click on the banner to return to the user guide home page.

2.2 The Architecture of Iostreams

This section will introduce you to iostreams: what they are, how they work, what kinds of problems they help solve, and how they are structured. Section 2.2.4 provides an overview of the class templates in iostreams. If you want to skip over the software architecture of iostreams, please go on to Section 2.3 on formatted input/output.

2.2.1 What Are the Standard Iostreams?

The Standard C++ Library includes classes for text stream input/output. Before the current ANSI/ISO standard, most C++ compilers were delivered with a class library commonly known as the iostreams library. In this section, we refer to this library as the traditional iostreams, in contrast to the standard iostreams that are now part of the ANSI/ISO Standard C++ Library. The standard iostreams are to some extent compatible with the traditional iostreams, in that the overall architecture and the most commonly used interfaces are retained. Section 2.14 describes the incompatibilities in greater detail.

We can compare the standard iostreams not only with the traditional C++ iostreams library, but also with the I/O support in the Standard C Library. Many former C programmers still prefer the input/output functions offered by the C library, often referred to as C stdio. Their familiarity with the C library is justification enough for using the C stdio instead of C++ iostreams, but there are other reasons as well. For example, calls to the C functions printf() and scanf() are admittedly more concise with C stdio. However, C stdio has drawbacks, too, such as type insecurity and inability to extend consistently for user-defined classes. We'll discuss these in more detail in the following sections.

2.2.1.1 Type Safety

Let us compare a call to stdio functions with the use of standard iostreams. The stdio call reads as follows:

int i = 25;
char name[50] = "Janakiraman";
fprintf(stdout, "%d %s", i, name);

It correctly prints: 25 Janakiraman.

But what if we inadvertently switch the arguments to fprintf? The error will be detected no sooner than run time. Anything can happen, from peculiar output to a system crash. This is not the case with the standard iostreams:

cout << i << ' ' << name << '\n';

Since there are overloaded versions of the shift operator operator<<(), the right operator will always be called. The function cout << i calls operator<<(int), and cout << name calls operator<<(const char*). Hence, the standard iostreams are typesafe.

2.2.1.2 Extensibility to New Types

Another advantage of the standard iostreams is that user-defined types can be made to fit in seamlessly. Consider a type Pair that we want to print:

struct Pair { int x; string y; }

All we need to do is overload operator<<() for this new type Pair, and we can output pairs this way:

Pair p(5, "May");
cout << p;

The corresponding operator<<() can be implemented as:

basic_ostream<char>& 
operator<<(basic_ostream<char>& o, const Pair& p)
{ return o << p.x << ' ' << p.y; }

2.2.2 How Do the Standard Iostreams Work?

The main purpose of the standard iostreams is to serve as a tool for input and output of text. Generally, input and output are the transfer of data between a program and any kind of external device, as illustrated in Figure 1 below:

Figure 1. Data transfer supported by iostreams


The internal representation of such data is meant to be convenient for data processing in a program. On the other hand, the external representation can vary quite a bit: it might be a display in human-readable form, or a portable data exchange format. The intent of a representation, such as conserving space for storage, can also influence the representation.

Text I/O involves the external representation of a sequence of characters; every other case involves binary I/O. Traditionally, iostreams are used for text processing. Such text processing through iostreams involves two processes: formatting and code conversion.

Formatting is the transformation from a byte sequence representing internal data into a human-readable character sequence; for example, from a floating point number, or an integer value held in a variable, into a sequence of digits. Figure 2 below illustrates the formatting process:

Figure 2. Formatting program data


Code conversion is the process of translating one character representation into another; for example, from wide characters held internally to a sequence of multibyte characters for external use. Wide characters are all the same size, and thus are convenient for internal data processing. Multibyte characters have different sizes and are stored more compactly. They are typically used for data transfer, or for storage on external devices such as files. Figure 3 below illustrates the conversion process:

Figure 3. Code conversion between multibytes and wide characters


2.2.2.1 The Iostream Layers

The iostreams facility has two layers: one that handles formatting, and another that handles code conversion and transport of characters to and from the external device. The layers communicate through a buffer, as illustrated in Figure 4 below:

Figure 4. The iostreams layers


Let's take a look at the function of each layer in more detail:

2.2.2.2 File and In-Memory I/O

Iostreams support two kinds of I/O: file I/O and in-memory I/O.

File I/O involves the transfer of data to and from an external device. The device need not necessarily be a file in the usual sense of the word. It could just as well be a communication channel, or another construct that conforms to the file abstraction.

In contrast, in-memory I/O involves no external device. Thus code conversion and transport are not necessary; only formatting is performed. The result of such formatting is maintained in memory, and can be retrieved in the form of a character string.

2.2.3 How Do the Standard Iostreams Help Solve Problems?

There are many situations in which iostreams are useful:

2.2.4 The Internal Structure of the Iostreams Layers

As explained earlier, iostreams have two layers, one for formatting, and another for code conversion and transport of characters to and from the external device. For convenience, let's repeat here in Figure 6 the illustration of the iostreams layers given in Figure 4 of Section 2.2.2:

Figure 6. The iostreams layers


This section will give a more detailed description of the iostreams software architecture, including the classes and their inheritance relationship and respective responsibilities. If you would rather start using iostreams directly, go on to Section 2.3.

2.2.4.1 The Internal Structure of the Formatting Layer

Classes that belong to the formatting layer are often referred to as the stream classes. Figure 7 illustrates the class hierarchy of all the stream classes:

Figure 7. Internal class hierarchy of the formatting layer [14]


Let us discuss in more detail the components and characteristics of the class hierarchy given in the figure:

2.2.4.2 The Transport Layer's Internal Structure

Classes of the transport layer are often referred to as the stream buffer classes. Figure 10 gives the class hierarchy of all stream buffer classes:

Figure 10. Hierarchy of the transport layer


The stream buffer classes are responsible for transfer of characters from and to external devices.

2.2.4.3 Collaboration of Streams and Stream Buffers

The base class basic_ios<> holds a pointer to a stream buffer. The derived stream classes, like file and string streams, contain a file or string buffer object. The stream buffer pointer of the base class refers to this embedded object. This architecture is illustrated in Figure 12 below:

Figure 12. How an input file stream uses a file buffer


Stream buffers can be used independently of streams, as for unformatted I/O, for example. However, streams always need a stream buffer.

2.2.4.4 Collaboration of Locales and Iostreams

The base class ios_base contains a locale object. The formatting and parsing functions defined by the derived stream classes use the numeric facets of that locale.

The class basic_ios<charT> holds a pointer to the stream buffer. This stream buffer has a locale object, too, usually a copy of the same locale object used by the functions of the stream classes. The stream buffer's input and output functions use the code conversion facet of the attached locale. Figure 13 below illustrates the architecture:

Figure 13. How an input file stream uses locales



©Copyright 1996, Rogue Wave Software, Inc.