DekGenius.com
Team LiB   Previous Section   Next Section

9.1 Introduction to I/O Streams

As with C and many modern languages, input and output in C++ is implemented entirely in the library. No language features specifically support I/O.

The C++ I/O library is based on a set of templates parameterized on the character type. Thus, you can read and write plain char-type characters, wide wchar_t characters, or some other, exotic character type that you might need to invent. (Read about character traits in Chapter 8 first.) Figure 9-1 depicts the class hierarchy. Notice that the names are of the form basic_name. These are the template names; the specializations have the more familiar names (e.g., istream specializes basic_istream<char>).

Figure 9-1. The I/O stream class hierarchy
figs/cppn_0901.gif

One advantage of using inheritance in the I/O library is that the basic I/O functions are defined once in the base classes, and that interface is inherited by the derived classes and overridden when necessary. Using inheritance, you perform I/O with files as you would with strings. With only a little effort, you can derive your own I/O classes for specialized situations. (See Example 9-6.) Thus, to understand I/O in C++, you must start with the base classes.

Another advantage of the I/O stream classes is that you can implement your own overloaded functions that look and behave like the standard functions. Thus, you can read and write objects of your custom classes just as easily as the fundamental types.

The ios_base class declares types and constants that are used throughout the I/O library. Formatting flags, I/O state flags, open modes, and seek directions are all declared in ios_base.

The basic_istream template declares input functions, and basic_ostream declares output functions. The basic_iostream template inherits input and output functions through multiple inheritance from basic_istream and basic_ostream.

The stream class templates handle high-level I/O of numbers, strings, and characters. For low-level I/O, the streams rely on stream buffers, which control reading and writing buffers of characters. The basic_streambuf template defines the stream buffer interface, and the actual behavior is implemented by derived-class templates.

I/O to and from external files is handled by basic_fstream, basic_ifstream, and basic_ofstream, using basic_filebuf as the stream buffer. These class templates are declared in <fstream>.

You can also treat a string as a stream using basic_istringstream, basic_ostringstream, and basic_stringstream. The stream buffer template is basic_stringbuffer. These class templates are declared in <sstream>.

The I/O library supports formatted and unformatted I/O. Unformatted I/O simply reads or writes characters or character strings without interpretation. The I/O streams have a number of functions for performing unformatted I/O.

Formatted input can skip over leading whitespace, parse text as numbers, and interpret numbers in different bases (decimal, octal, hexadecimal). Formatted output can pad fields to a desired width and write numbers as text in different bases. Formatted I/O uses a stream's locale to parse numeric input or format numeric output.

A locale is an object of type locale. It stores character attributes, formatting preferences, and related information about a particular culture or environment. This information is organized into a set of facets. For example, the num_get facet defines how numbers are read and parsed from an input stream; the num_put facet defines how numbers are formatted and written to an output stream; and the numpunct facet specifies the punctuation characters used for decimal points, minus signs, and so on. Locales are used primarily by I/O streams, but they have other uses, as well. See <locale> in Chapter 13 for details.

To perform formatted I/O, the I/O streams overload the shift operators: left shift (<<) writes and right shift (>>) reads. Think of the shift operators as arrows pointing in the direction of data flow: output flows from an expression to the stream (cout << expr). Input flows from the stream to a variable (cin >> var). The string, complex, and other types in the standard library overload the shift operators, so you can perform I/O with these objects just as easily as you can with the fundamental types.

9.1.1 Custom I/O Operators

When you define your own classes for which I/O is meaningful, you should also override the shift operators to perform I/O in the same manner as the standard I/O operators. A common simplification is to overload the shift operators for istream and ostream, but that prevents your operators from being used with wide-character streams or streams with custom character traits. You should consider writing function templates instead, using basic_istream and basic_ostream, to take advantage of the generality that the I/O stream templates offer.

Other guidelines to follow when writing custom I/O functions are:

  • Pay attention to the stream's flags, locale, and state.

  • Set failbit for malformed input.

  • Be careful with internal whitespace.

  • When writing an object that has multiple parts, be careful of how you treat the stream's field width. Remember that the width is reset to 0 after each formatted output function.

Example 9-1 shows an example of I/O for the rational class, which represents a rational number. For input, two numbers are read, separated by a slash (/). If the slash is missing, the input is malformed, so failbit is set. (See the <ios> header in Chapter 13 for more information about failbit and the other state bits.) For output, the two parts of the rational number are formatted as a string, and the entire string is written to the output stream. Using a temporary string lets the caller set the field width and apply the width to the entire rational number as a single entity. If the numerator and denominator were written directly to the output stream, the width would apply only to the numerator. (See Example 9-3 for more information about rational.)

Example 9-1. Performng I/O with the rational class template
// Read a rational number. The numerator and denominator must be written as two
// numbers (the first can be signed) separated by a slash (/). For example: 
// "2/3", "-14/19".
template<typename T, typename charT, typename traits>
std::basic_istream<charT, traits>&
operator>>(std::basic_istream<charT, traits>& in, rational<T>& r)
{
  typename rational<T>::numerator_type n;
  typename rational<T>::denominator_type d;
  char c;

  if (! (in >> n)) return in;
  // Allow whitespace before and after the dividing '/'.
  if (! (in >> c)) return in;
  if (c != '/') {
     // Malformed input
     in.setstate(std::ios_base::failbit);
     return in;
  }
  if (! (in >> d)) return in;
  r.set(n, d);
  return in;
}

// Write a rational number as two integers separated by a slash. Use a string
// stream so the two numbers are written without padding, and the overall
// formatted string is then padded to the desired width.
template<typename T, typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& out, const rational<T>& r)
{
  // Use the same flags, locale, etc. to write the
  // numerator and denominator to a string stream.
  std::basic_ostringstream<charT, traits> s;
  s.flags(out.flags(  ));
  s.imbue(out.getloc(  ));
  s.precision(out.precision(  ));
  s << r.numerator(  ) << '/' << r.denominator(  );
  // Write the string to out. The field width, padding, and alignment are already
  // set in out, so they apply to the entire rational number.
  out << s.str(  );
  return out;
}

9.1.2 The C Standard I/O Library

Because the C++ library includes the C library, you can use any of the C standard I/O functions, such as fopen and printf. The C standard I/O library is contained in <cstdio> for narrow I/O and <cwchar> for wide-character I/O. The C++ library inherits many attributes from the C library. For example, the C++ end-of-file marker, char_traits<char>::eof( ), has the same value as the C end-of-file marker, EOF. In most cases, however, a C++ program should use C++ I/O functions rather than C functions.

The printf and scanf functions are noteworthy for their lack of safety. Although some compilers now check the format strings and compare them with the actual arguments, many compilers do not. It is too easy to make a simple mistake. If you are lucky, your program will fail immediately. If you are unlucky, your program will appear to work on your system and fail only when it is run on your customers' systems. The following is an example of a common mistake made when using printf:

size_t s;
printf("size=%u\n", s);

The problem here is that the size_t type might be unsigned long, which means the argument s and the format %u do not match. On some systems, the mismatch is harmless, but on others, wrong information will be printed, or worse.

Other functions, such as gets and sprintf, are unsafe because they write to character arrays with no way to limit the number of characters written. Without a way to prevent buffer overruns, these functions are practically useless.

Another limitation of the C I/O library is that it has little support for alternate locales. In C++, every stream can have a different locale. For example, this lets you write a program that reads a datafile in a fixed format (using the "C" locale), but prints the human-readable results in the native locale. Writing such a program using the C I/O library requires that locales be changed between each read and write call, which is much less convenient.

In spite of all these problems, some C++ programmers still use the C library. In some implementations, the C library performs better than the C++ library. Another reason is that some C++ functions, such as printf, have a brevity that appeals to C and C++ programmers. Compare the following examples, one using printf and the other using the << operator, which both print the same count and mask values in the same formats:

unsigned long count, mask;
...
printf("count=%-9.9ld\n"
       "mask=%#-8.8lx\n", count, mask);

cout.fill('0');
cout << "count=" << right << dec << setw(9) << count <<
        "\nmask=0x" << hex << setw(8) << mask << '\n';

Even though the printf approach seems more concise and easier to understand, I recommend using the C++ I/O streams. You might think printf is saving you time now, but the lack of safety can present major problems in the future. (Reread the example and imagine what would happen if count exceeded the maximum value for type long.)

Sometimes, using the C I/O library is necessary (e.g., perhaps legacy C code is being called from C++ code). To help in such situations, the standard C++ I/O objects are associated with their corresponding C FILEs. The C++ cin object is associated with the C stdin object, cout is associated with stdout, and cerr and clog are associated with stderr. You can mix C and C++ I/O functions with the standard I/O objects.

9.1.3 C++ I/O Headers

This section lists the I/O-related headers in the C++ standard library, with a brief description of each. See the corresponding sections in Chapter 13 for more detailed descriptions.

<fstream>

File streams—that is, input and output using external files. Declares basic_filebuf, basic_fstream, basic_ifstream, basic_ofstream, fstream, ifstream, ofstream, wfstream, wistream, wostream, and other types.

<iomanip>

Declares several manipulators—that is, function objects that affect an I/O stream object. A manipulator offers an alternate, often more convenient, syntax for calling member functions of an I/O stream object.

<ios>

Base template definitions of ios_base, basic_ios and some common manipulators. All I/O streams derive from basic_ios, which derives from ios_base.

<iosfwd>

Forward declarations of the standard I/O classes and templates. Judicious use of <iosfwd> can reduce the compile-time burden in certain situations.

<iostream>

Declarations of the standard I/O objects: cin, cout, etc.

<istream>

Declares types and functions for input-only streams (basic_istream, istream, and wistream), and for input and output streams (basic_iostream, iostream, and wiostream).

<ostream>

Declares types and functions for output-only streams (basic_ostream, ostream, and wostream).

<sstream>

Declares string streams (basic_istringstream, basic_ostringstream, basic_stringbuf, basic_stringstream, istringstream, ostringstream, and stringstream), which read from and write to strings using the stream protocol.

<streambuf>

Declares low-level stream buffers (basic_streambuf) for the I/O stream classes. Most programs do not need to deal with the stream buffers, but stick to the high-level interfaces presented by the stream classes.

<strstream>

Declares string streams (istrstream, ostrstream, strstream, strstreambuf), which read and write to character arrays using a stream protocol. These classes are not template-based so they do not work for wide characters or alternative character traits. This header is deprecated.

    Team LiB   Previous Section   Next Section