Demystifying C++ I/O - Coding the Professional Way - Professional C++ (2014)

Professional C++ (2014)

Part IIICoding the Professional Way

Chapter 12Demystifying C++ I/O

WHAT’S IN THIS CHAPTER?

· What streams are

· How to use streams for input and output of data

· What the available standard streams are in the Standard Library

WROX.COM DOWNLOADS FOR THIS CHAPTER

Please note that all the code examples for this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com/go/proc++3e on the Download Code tab.

A program’s fundamental job is to accept input and produce output. A program that produces no output of any sort would not be very useful. All languages provide some mechanism for I/O, either as a built-in part of the language or through an OS-specific API. A good I/O system is both flexible and easy to use. Flexible I/O systems support input and output through a variety of devices, such as files and the user console. They also support reading and writing of different types of data. I/O is error-prone because data coming from a user can be incorrect or the underlying file system or other data source can be inaccessible. Thus, a good I/O system is also capable of handling error conditions.

If you are familiar with the C language, you have undoubtedly used printf() and scanf(). As I/O mechanisms, printf() and scanf() are certainly flexible. Through escape codes and variable placeholders, they can be customized to read in specially formatted data, or output any value that the formatting codes permit, which is currently limited to integer/character values, floating point values, and strings. However, printf() and scanf() falter on other measures of good I/O systems. They do not handle errors particularly well, they are not flexible enough to handle custom data types, and in an object-oriented language like C++, they are not at all object oriented.

C++ provides a more refined method of input and output through a mechanism known as streams. Streams are a flexible and object-oriented approach to I/O. In this chapter, you will learn how to use streams for data output and input. You will also learn how to use the stream mechanism to read from various sources and write to various destinations, such as the user console, files, and even strings. This chapter covers the most commonly used I/O features.

USING STREAMS

The stream metaphor takes a bit of getting used to. At first, streams may seem more complex than traditional C-style I/O, such as printf(). In reality, they seem complicated initially only because there is a deeper metaphor behind streams than there is behindprintf(). Don’t worry though; after a few examples, you’ll never look back.

What Is a Stream, Anyway?

Chapter 1 compares the cout stream like a laundry chute for data. You throw some variables down the stream, and they are written to the user’s screen, or console. More generally, all streams can be viewed as data chutes. Streams vary in their direction and their associated source or destination. For example, the cout stream that you are already familiar with is an output stream, so its direction is “out.” It writes data to the console so its associated destination is “console.” There is another standard stream called cin that accepts input from the user. Its direction is “in,” and its associated source is “console.” Both cout and cin are predefined instances of streams that are defined within the std namespace in C++. The following table gives a brief description of all predefined streams. The difference between buffered and unbuffered streams is explained in a later section:

STREAM

DESCRIPTION

cin

An input stream, reads data from the “input console.”

cout

A buffered output stream, writes data to the “output console.”

cerr

An unbuffered output stream, writes data to the “error console,” which is often the same as the “output console.”

clog

A buffered version of cerr.

Note that graphical user interface applications normally do not have a console; i.e., if you write something to cout, the user will not see it. If you are writing a library, you should never assume the existence of cout, cin, cerr or, clog because you never know if your library will be used in a console or in a GUI application.

NOTE Every input stream has an associated source. Every output stream has an associated destination.

Another important aspect of streams is that they include data but also have a so-called current position. The current position is the position in the stream where the next read or write operation will take place.

Stream Sources and Destinations

Streams as a concept can be applied to any object that accepts data or emits data. You could write a stream-based network class or stream-based access to a MIDI-based instrument. In C++, there are three common sources and destinations for streams.

You have already read many examples of user, or console, streams. Console input streams make programs interactive by allowing input from the user at run time. Console output streams provide feedback to the user and output results.

File streams, as the name implies, read data from a file system and write data to a file system. File input streams are useful for reading configuration data and saved files, or for batch processing file-based data. File output streams are useful for saving state and providing output. File streams subsume the functionality of the C functions fprintf(), fwrite(), and fputs() for output, and fscanf(), fread(), and fgets() for input.

String streams are an application of the stream metaphor to the string type. With a string stream, you can treat character data just as you would treat any other stream. For the most part, this is merely a handy syntax for functionality that could be handled through methods on the string class. However, using stream syntax provides opportunities for optimization and can be far more convenient than direct use of the string class. String streams subsume the functionality of sprintf(), sprintf_s(), sscanf(), and other forms of C string formatting functions.

The rest of this section deals with console streams (cin and cout). Examples of file and string streams are provided later in this chapter. Other types of streams, such as printer output or network I/O are often platform dependent, so they are not covered in this book.

Output with Streams

Output using streams is introduced in Chapter 1 and is used in almost every chapter in this book. This section briefly revisits some of the basics and introduces material that is more advanced.

Output Basics

Output streams are defined in the <ostream> header file. Most programmers include <iostream> in their programs, which in turn includes the headers for both input streams and output streams. The <iostream> header also declares the standard console output stream, cout.

The << operator is the simplest way to use output streams. C++ basic types, such as ints, pointers, doubles, and characters, can be output using <<. In addition, the C++ string class is compatible with <<, and C-style strings are properly output as well. Following are some examples of using <<:

int i = 7;

cout << i << endl;

char ch = 'a';

cout << ch << endl;

string myString = "Hello World.";

cout << myString << endl;

The output is as follows:

7

a

Hello World.

The cout stream is the built-in stream for writing to the console, or standard output. You can “chain” uses of << together to output multiple pieces of data. This is because the << operator returns a reference to the stream as its result so you can immediately use << again on the same stream. For example:

int j = 11;

cout << "The value of j is " << j << "!" << endl;

The output is as follows:

The value of j is 11!

C++ streams correctly parse C-style escape codes, such as strings that contain \n. You can also use std::endl to start a new line. The difference between using \n and endl is that \n just starts a new line while endl also flushes the buffer. Watch out with endl because too many flushes might hurt performance. The following example uses endl to output and flush several lines of text with just one line of code.

cout << "Line 1" << endl << "Line 2" << endl << "Line 3" << endl;

The output is as follows:

Line 1

Line 2

Line 3

Methods of Output Streams

The << operator is, without a doubt, the most useful part of output streams. However, there is additional functionality to be explored. If you take a peek at the <ostream> header file, you’ll see many lines of overloaded definitions of the << operator. You’ll also find some useful public methods.

put() and write()

put() and write() are raw output methods. Instead of taking an object or variable that has some defined behavior for output, put() accepts a single character, while write() accepts a character array. The data passed to these methods is output as is, without any special formatting or processing. For example, the following function takes a C-style string and outputs it to the console without using the << operator:

void rawWrite(const char* data, int dataSize)

{

cout.write(data, dataSize);

}

The next function writes the given index of a C-style string to the console by using the put() method:

void rawPutChar(const char* data, int charIndex)

{

cout.put(data[charIndex]);

}

flush()

When you write to an output stream, the stream does not necessarily write the data to its destination right away. Most output streams buffer, or accumulate data instead of writing it out as it comes in. The stream will flush, or write out the accumulated data, when one of the following conditions occurs:

· A sentinel, such as the endl marker, is reached.

· The stream goes out of scope and is destructed.

· Input is requested from a corresponding input stream (i.e., when you make use of cin for input, cout will flush). In the section on file streams, you learn how to establish this type of link.

· The stream buffer is full.

· You explicitly tell the stream to flush its buffer.

One way to explicitly tell a stream to flush is to call its flush() method, as in the code that follows:

cout << "abc";

cout.flush(); // abc is written to the console.

cout << "def";

cout << endl; // def is written to the console.

NOTE Not all output streams are buffered. The cerr stream, for example, does not buffer its output.

Handling Output Errors

Output errors can arise in a variety of situations. Perhaps you are trying to open a non-existing file. Maybe a disk error has prevented a write operation from succeeding, for example because the disk is full. None of the streams’ code you have read up until this point has considered these possibilities, mainly for brevity. However, it is vital that you address any error conditions that occur.

When a stream is in its normal usable state, it is said to be “good.” The good() method can be called directly on a stream to determine whether or not the stream is currently good.

if (cout.good()) {

cout << "All good" << endl;

}

The good() method provides an easy way to obtain basic information about the validity of the stream, but it does not tell you why the stream is unusable. There is a method called bad() that provides a bit more information. If bad() returns true, it means that a fatal error has occurred (as opposed to any nonfatal condition like end-of-file). Another method, fail(), returns true if the most recent operation has failed; however, it doesn’t say anything about the next operation which can either succeed or fail as well. For example, after calling flush() on an output stream, you could call fail() to make sure the flush was successful.

cout.flush();

if (cout.fail()) {

cerr << "Unable to flush to standard out" << endl;

}

image You can also tell the streams to throw exceptions when a failure occurs. You then write a catch handler to catch ios_base::failure exceptions on which you can use the what() method to get a description of the error and the code() method to get the error code. However, whether or not you get useful information depends on the STL implementation that you use.

cout.exceptions(ios::failbit | ios::badbit | ios::eofbit);

try {

cout << "Hello World." << endl;

} catch (const ios_base::failure& ex) {

cerr << "Caught exception: " << ex.what()

<< ", error code = " << ex.code() << endl;

}

To reset the error state of a stream, use the clear() method:

cout.clear();

Error checking is performed less frequently for console output streams than for file output streams or input streams. The methods discussed here apply for other types of streams as well and are revisited later as each type is discussed.

Output Manipulators

One of the unusual features of streams is that you can throw more than just data down the chute. C++ streams also recognize manipulators, objects that make a change to the behavior of the stream instead of, or in addition to, providing data for the stream to work with.

You have already seen one manipulator: endl. The endl manipulator encapsulates data and behavior. It tells the stream to output an end-of-line sequence and to flush its buffer. Following are some other useful manipulators, many of which are defined in the <ios> and<iomanip> standard header files. The example after this list shows how to use them:

· boolalpha and noboolalpha. Tells the stream to output bool values as true and false (boolalpha) or 1 and 0 (noboolalpha). The default is noboolalpha.

· hex, oct, and dec. Outputs numbers in hexadecimal, octal, and base 10, respectively.

· setprecision. Sets the number of decimal places that are output for fractional numbers. This is a parameterized manipulator (meaning that it takes an argument).

· setw. Sets the field width for outputting numerical data. This is a parameterized manipulator.

· setfill. Specifies the character that is used to pad numbers that are smaller than the specified width. This is a parameterized manipulator.

· showpoint and noshowpoint. Forces the stream to always or never show the decimal point for floating point numbers with no fractional part.

· put_money. Writes a formatted money amount to a stream.

· put_time. Writes a formatted time to a stream.

· image quoted. A parameterized manipulator that encloses a given string with quotes and escapes embedded quotes.

All the above manipulators stay in effect for subsequent output to the stream until they are reset, except setw which is only active for the next single output. The following example uses several of these manipulators to customize its output.

// Boolean values

bool myBool = true;

cout << "This is the default: " << myBool << endl;

cout << "This should be true: " << boolalpha << myBool << endl;

cout << "This should be 1: " << noboolalpha << myBool << endl;

// Simulate "%6d" with streams

int i = 123;

printf("This should be ' 123': %6d\n", i);

cout << "This should be ' 123': " << setw(6) << i << endl;

// Simulate "%06d" with streams

printf("This should be '000123': %06d\n", i);

cout << "This should be '000123': " << setfill('0') << setw(6) << i << endl;

// Fill with *

cout << "This should be '***123': " << setfill('*') << setw(6) << i << endl;

// Reset fill character

cout << setfill(' ');

// Floating point values

double dbl = 1.452;

double dbl2 = 5;

cout << "This should be ' 5': " << setw(2) << noshowpoint << dbl2 << endl;

cout << "This should be @@1.452: " << setw(7) << setfill('@') << dbl << endl;

// Reset fill character

cout << setfill(' ');

// Instructs cout to start formatting numbers according to your location.

// Chapter 18 explains the details of the imbue call and the locale object.

cout.imbue(locale(""));

// Format numbers according to your location

cout << "This is 1234567 formatted according to your location: " << 1234567 << endl;

// Money amount

cout << "This should be a money amount of 120000, "

<< "formatted according to your location: "

<< put_money("120000") << endl;

// Date and time

time_t t_t = time(nullptr); // Get current system time

tm* t = localtime(&t_t); // Convert to local time

cout << "This should be the current date and time "

<< "formatted according to your location: "

<< put_time(t, "%c") << endl;

// C++14: Quoted string

cout << "This should be: \"Quoted string with \\\"embedded quotes\\\".\": "

<< quoted("Quoted string with \"embedded quotes\".") << endl;

NOTE This example might give you a security-related error or warning on the call to localtime(). With Microsoft Visual Studio you can use the safe version called localtime_s(). On Linux you can use localtime_r().

If you don’t care for the concept of manipulators, you can usually get by without them. Streams provide much of the same functionality through equivalent methods like precision(). For example, take the following line:

cout << "This should be '1.2346': " << setprecision(5) << 1.23456789 << endl;

This can be converted to use a method call as follows. The advantage of the method calls is that they return the previous value, allowing you to restore it.

cout.precision(5);

cout << "This should be '1.2346': " << 1.23456789 << endl;

For a detailed description of all stream methods and manipulators consult a Standard Library Reference, for example http://www.cppreference.com/ or http://www.cplusplus.com/reference/.

Input with Streams

Input streams provide a simple way to read in structured or unstructured data. In this section, the techniques for input are discussed within the context of cin, the console input stream.

Input Basics

There are two easy ways to read data by using an input stream. The first is an analog of the << operator that outputs data to an output stream. The corresponding operator for reading data is >>. When you use >> to read data from an input stream, the variable you provide is the storage for the received value. For example, the following program reads one word from the user and puts it into a string. Then the string is output back to the console:

string userInput;

cin >> userInput;

cout << "User input was " << userInput << endl;

By default, the >> operator tokenizes values according to white space. For example, if a user runs the previous program and enters hello there as input, only the characters up to the first white space character (the space character in this instance) will be captured into the userInput variable. The output would be as follows:

User input was hello

One solution to include white space in the input is to use get(), discussed later in this chapter.

The >> operator works with different variable types, just like the << operator. For example, to read an integer, the code differs only in the type of the variable:

int userInput;

cin >> userInput;

cout << "User input was " << userInput << endl;

You can use input streams to read in multiple values, mixing and matching types as necessary. For example, the following function, an excerpt from a restaurant reservation system, asks the user for a last name and the number of people in their party:

void getReservationData()

{

string guestName;

int partySize;

cout << "Name and number of guests: ";

cin >> guestName >> partySize;

cout << "Thank you, " << guestName << "." << endl;

if (partySize > 10) {

cout << "An extra gratuity will apply." << endl;

}

}

Remember that the >> operator tokenizes values according to white space, so the getReservationData() function does not allow you to enter a name with white space. A solution using unget() is discussed later in this chapter. Note also that even though the use of coutdoes not explicitly flush the buffer using endl or flush(), the text will still be written to the console because the use of cin immediately flushes the cout buffer; they are linked together in this way.

NOTE If you get confused between << and >>, just think of the angles as pointing toward their destination. In an output stream, << points toward the stream itself because data is being sent to the stream. In an input stream, >> points toward the variables because data is being stored.

Input Methods

Just like output streams, input streams have several methods that allow a lower level of access than the functionality provided by the more common >> operator.

get()

The get() method allows raw input of data from a stream. The simplest version of get() returns the next character in the stream, though other versions exist that read multiple characters at once. get() is most commonly used to avoid the automatic tokenization that occurs with the >> operator. For example, the following function reads a name, which can be made up of several words, from an input stream until the end of the stream is reached:

string readName(istream& inStream)

{

string name;

while (!inStream.fail()) {

int next = inStream.get();

if (next == std::char_traits<char>::eof())

break;

name += static_cast<char>(next);// Append character.

}

return name;

}

There are several interesting observations to make about this readName() function:

· Its parameter is a non-const reference to an istream, not a const reference. The methods that read in data from a stream will change the actual stream (most notably, its position), so they are not const methods. Thus, you can’t call them on a const reference.

· The return value of get() is stored in an int, not in a char. Because get() can return special non-character values such as std::char_traits<char>::eof() (end-of-file), ints are used.

readName() is a bit strange because there are two ways to get out of the loop. Either the stream can get into a failed state, or the end of the stream is reached. A more common pattern for reading from a stream uses a different version of get() that takes a reference to a character and returns a reference to the stream. This pattern takes advantage of the fact that evaluating an input stream within a conditional context results in true only if the stream is available for additional reading. Encountering an error or reaching the end-of-file both cause the stream to evaluate to false. The underlying details of the conversion operations required to implement this feature are explained in Chapter 14. The following version of the same function is a bit more concise:

string readName(istream& inStream)

{

string name;

char next;

while (inStream.get(next)) {

name += next;

}

return name;

}

unget()

For most purposes, the correct way to think of an input stream is as a one-way chute. Data falls down the chute and into variables. The unget() method breaks this model in a way by allowing you to push data back up the chute.

A call to unget() causes the stream to back up by one position, essentially putting the previous character read back on the stream. You can use the fail() method to see if unget() was successful or not. For example, unget() can fail if the current position is at the beginning of the stream.

The getReservationData() function seen earlier in this chapter did not allow you to enter a name with white space. The following code uses unget() to allow white space in the name. The code reads character by character and checks whether the character is a digit or not. If the character is not a digit, it is added to guestName. If it is a digit, the character is put back into the stream using unget(), the loop is stopped, and the >> operator is used to input an integer, partySize. The meaning of noskipws is discussed later in the section “Input Manipulators.”

void getReservationData()

{

string guestName;

int partySize = 0;

// Read characters until we find a digit

char ch;

cin >> noskipws;

while (cin >> ch) {

if (isdigit(ch)) {

cin.unget();

if (cin.fail())

cout << "unget() failed" << endl;

break;

}

guestName += ch;

}

// Read partysize

cin >> partySize;

cout << "Thank you '" << guestName

<< "', party of " << partySize << endl;

if (partySize > 10) {

cout << "An extra gratuity will apply." << endl;

}

}

putback()

putback(), like unget(), lets you move backward by one character in an input stream. The difference is that the putback() method takes the character being placed back on the stream as a parameter:

char ch1;

cin >> ch1;

cin.putback(ch1);

// ch1 will be the next character read off the stream.

peek()

The peek() method allows you to preview the next value that would be returned if you were to call get(). To take the chute metaphor perhaps a bit too far, you could think of it as looking up the chute without a value actually falling down it.

peek() is ideal for any situation where you need to look ahead before reading a value. For example, the following code implements the getReservationData() function that allows white space in the name, but uses peek() instead of unget():

void getReservationData()

{

string guestName;

int partySize = 0;

// Read characters until we find a digit

char ch;

cin >> noskipws;

while (true) {

// 'peek' at next character

ch = static_cast<char>(cin.peek());

if (!cin.good())

break;

if (isdigit(ch)) {

// next character will be a digit, so stop the loop

break;

}

// next character will be a non-digit, so read it

cin >> ch;

guestName += ch;

}

// Read partysize

cin >> partySize;

cout << "Thank you '" << guestName

<< "', party of " << partySize << endl;

if (partySize > 10) {

cout << "An extra gratuity will apply." << endl;

}

}

getline()

Obtaining a single line of data from an input stream is so common that a method exists to do it for you. The getline() method fills a character buffer with a line of data up to the specified size. The specified size includes the \0 character. Thus, the following code will read a maximum of kBufferSize-1 characters from cin, or until an end-of-line sequence is read:

char buffer[kBufferSize];

cin.getline(buffer, kBufferSize);

When getline() is called, it reads a line from the input stream, up to and including the end-of-line sequence. However, the end-of-line character or characters do not appear in the string. Note that the end-of-line sequence is platform dependent. For example, it can be \r\n, or \n, or \n\r.

There is a form of get() that performs the same operation as getline(), except that it leaves the newline sequence in the input stream.

There is also a function called getline() that can be used with C++ strings. It is defined in the <string> header file and is in the std namespace. It takes a stream reference, a string reference, and an optional delimiter as parameters. The advantage of using this version of getline() is that it doesn’t require you to specify the size of the buffer.

string myString;

std::getline(cin, myString);

Handling Input Errors

Input streams have a number of methods to detect unusual circumstances. Most of the error conditions related to input streams occur when there is no data available to read. For example, the end of stream (referred to as end-of-file, even for non-file streams) may have been reached. The most common way of querying the state of an input stream is to access it within a conditional. For example, the following loop will keep looping as long as cin remains in a good state:

while (cin) { ... }

You can input data at the same time:

while (cin >> ch) { ... }

You can also call the good(), bad(), and fail() methods, just like on output streams. There is also an eof() method that returns true if the stream has reached its end.

You should also get into the habit of checking the stream state after reading data so that you can recover from bad input.

The following program shows the common pattern for reading data from a stream and handling errors. The program reads numbers from standard input and displays their sum once end-of-file is reached. Note that in command-line environments, the end-of-file is indicated by the user typing a particular character. In Unix and Linux, it is Control+D, in Windows it is Control+Z. The exact character is operating system dependent, so you will need to know what your operating system requires:

cout << "Enter numbers on separate lines to add. "

<< "Use Control+D to finish (Control+Z in Windows)." << endl;

int sum = 0;

if (!cin.good()) {

cerr << "Standard input is in a bad state!" << endl;

return 1;

}

int number;

while (!cin.bad()) {

cin >> number;

if (cin.good()) {

sum += number;

} else if (cin.eof()) {

break; // Reached end of file

} else if (cin.fail()) {

// Failure!

cin.clear(); // Clear the failure state.

string badToken;

cin >> badToken; // Consume the bad input.

cerr << "WARNING: Bad input encountered: " << badToken << endl;

}

}

cout << "The sum is " << sum << endl;

Input Manipulators

The built-in input manipulators, described in the list that follows, can be sent to an input stream to customize the way that data is read.

· boolalpha and noboolalpha. If boolalpha is used, the string false will be interpreted as a Boolean value false; anything else will be treated as the Boolean value true. If noboolalpha is set, 0 will be interpreted as false, anything else as true. The default is noboolalpha.

· hex, oct, and dec. Reads numbers in hexadecimal, octal, and base 10, respectively.

· skipws and noskipws. Tells the stream to either skip white space when tokenizing or to read in white space as its own token.

· ws. A handy manipulator that simply skips over the current series of white space at the current position in the stream.

· get_money. Reads a money amount from a stream.

· get_time. Reads a formatted time from a stream.

· image quoted. A parameterized manipulator that reads a string enclosed with quotes and in which embedded quotes are escaped.

Input is locale aware. For example, the following code enables your system locale for cin. Locales are discussed in Chapter 18:

cin.imbue(locale(""));

int i;

cin >> i;

If your system locale is U.S. English, you can enter 1,000 and it will be parsed as 1000. On the other hand, if your system locale is Dutch Belgium, you should enter 1.000 to get the value of 1000.

Input and Output with Objects

You can use the << operator to output a C++ string even though it is not a basic type. In C++, objects are able to prescribe how they are output and input. This is accomplished by overloading the << and >> operator to understand a new type or class.

Why would you want to overload these operators? If you are familiar with the printf() function in C, you know that it is not flexible in this area. printf() knows about several types of data, but there really isn’t a way to give it additional knowledge. For example, consider the following simple class:

class Muffin

{

public:

const string& getDescription() const;

void setDescription(const string& inDesc);

int getSize() const;

void setSize(int inSize);

bool getHasChocolateChips() const;

void setHasChocolateChips(bool inChips);

private:

string mDesc;

int mSize;

bool mHasChocolateChips;

};

const string& Muffin::getDescription() const { return mDesc; }

void Muffin::setDescription(const string& inDesc) { mDesc = inDesc; }

int Muffin::getSize() const { return mSize; }

void Muffin::setSize(int inSize) { mSize = inSize; }

bool Muffin::getHasChocolateChips() const { return mHasChocolateChips; }

void Muffin::setHasChocolateChips(bool inChips) { mHasChocolateChips = inChips; }

To output an object of class Muffin by using printf(), it would be nice if you could specify it as an argument, perhaps using %m as a placeholder:

printf("Muffin output: %m\n", myMuffin); // BUG! printf doesn't understand Muffin.

Unfortunately, the printf() function knows nothing about the Muffin type and is unable to output an object of type Muffin. Worse still, because of the way the printf() function is declared, this will result in a run-time error, not a compile-time error (though a good compiler will give you a warning).

The best you can do with printf() is to add a new output() method to the Muffin class:

class Muffin

{

public:

const string& getDescription() const;

void setDescription(const string& inDesc);

int getSize() const;

void setSize(int inSize);

bool getHasChocolateChips() const;

void setHasChocolateChips(bool inChips);

void output();

private:

string mDesc;

int mSize;

bool mHasChocolateChips;

};

// Other method implementations omitted for brevity

void Muffin::output()

{

printf("%s, Size is %d, %s\n", getDescription().c_str(), getSize(),

(getHasChocolateChips() ? "has chips" : "no chips"));

}

Using such a mechanism is cumbersome, however. To output a Muffin in the middle of another line of text, you’d need to split the line into two calls with a call to Muffin::output() in between, as shown in the following:

printf("The muffin is ");

myMuffin.output();

printf(" -- yummy!\n");

Overloading the << operator lets you output a Muffin just like you output a string — by providing it as an argument to <<. Chapter 14 covers the details of overloading the << and >> operators.

STRING STREAMS

String streams provide a way to use stream semantics with strings. In this way, you can have an in-memory stream that represents textual data. For example, in a GUI application you might want to use streams to build up textual data, but instead of outputting the text to the console or a file, you might want to display the result in a GUI element like a message box or an edit control. Another example could be that you want to pass a string stream around to different functions, while retaining the current read position, so that each function can process the next part of the stream. String streams are also useful for parsing text, because streams have built-in tokenizing functionality.

The ostringstream class is used to write data to a string, while the istringstream class is used to read data from a string. They are both defined in the <sstream> header file. Because ostringstream and istringstream inherit the same behavior as ostream and istream, working with them is pleasantly similar.

The following program requests words from the user and outputs them to a single ostringstream, separated by the tab character. At the end of the program, the whole stream is turned into a string object using the str() method and is written to the console. Input of tokens can be stopped by entering the token “done” or by closing the input stream with Control+D (Unix) or Control+Z (Windows):

cout << "Enter tokens. Control+D (Unix) or Control+Z (Windows) to end" << endl;

ostringstream outStream;

while (cin) {

string nextToken;

cout << "Next token: ";

cin >> nextToken;

if (nextToken == "done")

break;

outStream << nextToken << "\t";

}

cout << "The end result is: " << outStream.str();

Reading data from a string stream is similarly familiar. The following function creates and populates a Muffin object (see earlier example) from a string input stream. The stream data is in a fixed format so that the function can easily turn its values into calls to theMuffin setters:

Muffin createMuffin(istringstream& inStream)

{

Muffin muffin;

// Assume data is properly formatted:

// Description size chips

string description;

int size;

bool hasChips;

// Read all three values. Note that chips is represented

// by the strings "true" and "false"

inStream >> description >> size >> boolalpha >> hasChips;

muffin.setSize(size);

muffin.setDescription(description);

muffin.setHasChocolateChips(hasChips);

return muffin;

}

NOTE Turning an object into a “flattened” type, like a string, is often called marshalling. Marshalling is useful for saving objects to disk or sending them across a network.

The main advantage of a string stream over a standard C++ string is that, in addition to data, the object knows where the next read or write operation will take place, also called current position. There may also be performance benefits depending on the particular implementation of string streams. For example, if you need to append a lot of strings together, it might be more efficient to use a string stream, instead of repeatedly calling the += operator on a string object.

FILE STREAMS

Files lend themselves very well to the stream abstraction because reading and writing files always involves a position in addition to the data. In C++, the ofstream and ifstream classes provide output and input functionality for files. They are defined in the <fstream>header file.

When dealing with the file system, it is especially important to detect and handle error cases. The file you are working with could be on a network file store that just went offline, or you may be trying to write to a file that is located on a disk that is full. Maybe you are trying to open a file to which the current user does not have permissions to. Error conditions can be detected by using the standard error handling mechanisms described earlier.

The only major difference between output file streams and other output streams is that the file stream constructor can take the name of the file and the mode in which you would like to open it. The default mode is write, ios_base::out, which starts writing to a file at the beginning, overwriting any existing data. You can also open an output file stream in append mode by specifying the constant ios_base::app as second argument to the file stream constructor. The following table lists the different constants that are available:

CONSTANT

DESCRIPTION

ios_base::app

Open and go to the end before each write operation.

ios_base::ate

Open and go to the end immediately after opening.

ios_base::binary

Perform input and output in binary mode (as opposed to text mode).

ios_base::in

Open for input, start reading at the beginning.

ios_base::out

Open for output, start writing at the beginning, overwriting existing data.

ios_base::trunc

Open for output, delete all existing data (truncate).

The following program opens the file test.txt and outputs the arguments to the program. The ifstream and ofstream destructors automatically close the underlying file, so there is no need to explicitly call close():

int main(int argc, char* argv[])

{

ofstream outFile("test.txt", ios_base::trunc);

if (!outFile.good()) {

cerr << "Error while opening output file!" << endl;

return -1;

}

outFile << "There were " << argc << " arguments to this program." << endl;

outFile << "They are: " << endl;

for (int i = 0; i < argc; i++) {

outFile << argv[i] << endl;

}

return 0;

}

Jumping around with seek() and tell()

The seek() and tell() methods are present on all input and output streams, but they rarely make sense outside of the context of file streams.

The seek() methods let you move to an arbitrary position within an input or output stream. There are several forms of seek(). The methods of seek() within an input stream are actually called seekg() (the g is for get), and the versions of seek() in an output stream are called seekp() (the p is for put). You might wonder why there is both a seekg() and a seekp() method, instead of one seek() method. The reason is that you can have streams that are both input and output, for example, file streams. In that case, the stream needs to remember both a read position and a separate write position. This is also called bidirectional I/O and is covered later in this chapter.

There are two overloads of seekg() and two of seekp(). One overload accepts a single argument, an absolute position, and seeks to this absolute position. The second overload accepts an offset and a position, and seeks an offset relative to the given position. Positions are of type ios_base::streampos, while offsets are of type ios_base::streamoff, both are measured in bytes. There are three predefined positions available:

POSITION

DESCRIPTION

ios_base::beg

The beginning of the stream

ios_base::end

The end of the stream

ios_base::cur

The current position in the stream

For example, to seek to an absolute position in an output stream, you can use the one-parameter version of seekp(), as in the following case, which uses the constant ios_base::beg to move to the beginning of the stream:

outStream.seekp(ios_base::beg);

Seeking within an input stream is exactly the same, except that the seekg() method is used:

inStream.seekg(ios_base::beg);

The two-argument versions move to a relative position in the stream. The first argument prescribes how many positions to move and the second argument provides the starting point. To move relative to the beginning of the file, the constant ios_base::beg is used. To move relative to the end of the file, ios_base::end is used. To move relative to the current position, ios_base::cur is used. For example, the following line moves to the second byte from the beginning of the stream. Note that integers are implicitly converted to typeios_base::streampos and ios_base::streamoff:

outStream.seekp(2, ios_base::beg);

The next example moves to the third-to-last byte of an input stream.

inStream.seekg(-3, ios_base::end);

You can also query a stream’s current location using the tell() method which returns an ios_base::streampos that indicates the current position. You can use this result to remember the current marker position before doing a seek() or to query whether you are in a particular location. As with seek(), there are separate versions of tell() for input streams and output streams. Input streams use tellg(), and output streams use tellp().

The following code checks the position of an input stream to determine if it is at the beginning.

ios_base::streampos curPos = inStream.tellg();

if (ios_base::beg == curPos) {

cout << "We're at the beginning." << endl;

}

Following is a sample program that brings it all together. This program writes into a file called test.out and performs the following tests:

1. Outputs the string 12345 to the file.

2. Verifies that the marker is at position 5 in the stream.

3. Moves to position 2 in the output stream.

4. Outputs a 0 in position 2 and closes the output stream.

5. Opens an input stream on the test.out file.

6. Reads the first token as an integer.

7. Confirms that the value is 12045.

ofstream fout("test.out");

if (!fout) {

cerr << "Error opening test.out for writing" << endl;

return 1;

}

// 1. Output the string "12345".

fout << "12345";

// 2. Verify that the marker is at position 5.

ios_base::streampos curPos = fout.tellp();

if (5 == curPos) {

cout << "Test passed: Currently at position 5" << endl;

} else {

cout << "Test failed: Not at position 5" << endl;

}

// 3. Move to position 2 in the stream.

fout.seekp(2, ios_base::beg);

// 4. Output a 0 in position 2 and close the stream.

fout << 0;

fout.close();

// 5. Open an input stream on test.out.

ifstream fin("test.out");

if (!fin) {

cerr << "Error opening test.out for reading" << endl;

return 1;

}

// 6. Read the first token as an integer.

int testVal;

fin >> testVal;

// 7. Confirm that the value is 12045.

const int expected = 12045;

if (testVal == expected) {

cout << "Test passed: Value is " << expected << endl;

} else {

cout << "Test failed: Value is not " << expected

<< " (it was " << testVal << ")" << endl;

}

Linking Streams Together

A link can be established between any input and output streams to give them flush-on-access behavior. In other words, when data is requested from an input stream, its linked output stream will automatically flush. This behavior is available to all streams, but is particularly useful for file streams that may be dependent upon each other.

Stream linking is accomplished with the tie() method. To tie an output stream to an input stream, call tie() on the input stream, and pass the address of the output stream. To break the link, pass nullptr.

The following program ties the input stream of one file to the output stream of an entirely different file. You could also tie it to an output stream on the same file, but bidirectional I/O (covered below) is perhaps a more elegant way to read and write the same file simultaneously.

ifstream inFile("input.txt"); // Note: input.txt must exist.

ofstream outFile("output.txt");

// Set up a link between inFile and outFile.

inFile.tie(&outFile);

// Output some text to outFile. Normally, this would

// not flush because std::endl is not sent.

outFile << "Hello there!";

// outFile has NOT been flushed.

// Read some text from inFile. This will trigger flush()

// on outFile.

string nextToken;

inFile >> nextToken;

// outFile HAS been flushed.

The flush() method is defined on the ostream base class, so you can also link an output stream to another output stream:

outFile.tie(&anotherOutputFile);

Such a relationship would mean that every time you wrote to one file, the buffered data that had been sent to the other file would be written. You could use this mechanism to keep two related files synchronized.

One example of this stream linking is the link between cout and cin. Whenever you try to input data from cin, cout is automatically flushed.

BIDIRECTIONAL I/O

So far, this chapter has discussed input and output streams as two separate but related classes. In fact, there is such a thing as a stream that performs both input and output. A bidirectional stream operates as both an input stream and an output stream.

Bidirectional streams are deriving from iostream, which in turn derives from both istream and ostream, thus serving as an example of useful multiple inheritance. As you would expect, bidirectional streams support both the >> operator and the << operator, as well as the methods of both input streams and output streams.

The fstream class provides a bidirectional file stream. fstream is ideal for applications that need to replace data within a file because you can read until you find the correct position, then immediately switch to writing. For example, imagine a program that stores a list of mappings between ID numbers and phone numbers. It might use a data file with the following format:

123 408-555-0394

124 415-555-3422

263 585-555-3490

100 650-555-3434

A reasonable approach to such a program would be to read in the entire data file when the program opens and rewrite the file, with any modifications, when the program closes. If the data set is huge, however, you might not be able to keep everything in memory. With iostreams, you don’t have to. You can easily scan through the file to find a record, and you can add new records by opening the file for output in append mode. To modify an existing record, you could use a bidirectional stream, as in the following function that changes the phone number for a given ID:

bool changeNumberForID(const string& inFileName, int inID,

const string& inNewNumber)

{

fstream ioData(inFileName.c_str());

if (!ioData) {

cerr << "Error while opening file " << inFileName << endl;

return false;

}

// Loop until the end of file

while (ioData.good()) {

int id;

string number;

// Read the next ID.

ioData >> id;

// Check to see if the current record is the one being changed.

if (id == inID) {

// Seek the write position to the current read position

ioData.seekp(ioData.tellg());

// Output a space, then the new number.

ioData << " " << inNewNumber;

break;

}

// Read the current number to advance the stream.

ioData >> number;

}

return true;

}

Of course, an approach like this will work properly only if the data is of a fixed size. When the preceding program switched from reading to writing, the output data overwrote other data in the file. To preserve the format of the file, and to avoid writing over the next record, the data had to be the exact same size.

String streams can also be accessed in a bidirectional manner through the stringstream class.

NOTE Bidirectional streams have separate pointers for the read position and the write position. When switching between reading and writing, you will need to seek to the appropriate position.

SUMMARY

Streams provide a flexible and object-oriented way to perform input and output. The most important message in this chapter, even more important than the use of streams, is the concept of a stream. Some operating systems may have their own file access and I/O facilities, but knowledge of how streams and stream-like libraries work is essential to working with any type of modern I/O system.