16
STREAMS

Either write something worth reading or do something worth writing.
—Benjamin Franklin

Image

This chapter introduces streams, the major concept that enables you to connect inputs from any kind of source and outputs to any kind of destination using a common framework. You’ll learn about the classes that form the base elements of this common framework, several built-in facilities, and how to incorporate streams into user-defined types.

Streams

A stream models a stream of data. In a stream, data flows between objects, and those objects can perform arbitrary processing on the data. When you’re working with streams, output is data going into the stream and input is data coming out of the stream. These terms reflect the streams as viewed from the user’s perspective.

In C++, streams are the primary mechanism for performing input and output (I/O). Regardless of the source or destination, you can use streams as the common language to connect inputs to outputs. The STL uses class inheritance to encode the relationships between various stream types. The primary types in this hierarchy are:

  • The std::basic_ostream class template in the <ostream> header that represents an output device
  • The std::basic_istream class template in the <istream> header that represents an input device
  • The std::basic_iostream class template in the <iostream> header for devices that are input and output

All three stream types require two template parameters. The first corresponds to the stream’s underlying data type and the second to a traits type.

This section covers streams from a user’s perspective rather than from a library implementer’s perspective. You’ll understand the streams interface and know how to interact with standard I/O, files, and strings using the STL’s built-in stream support. If you must implement a new kind of stream (for example, for a new library or framework), you’ll need a copy of the ISO C++ 17 Standard, some working examples, and an ample supply of coffee. I/O is complicated, and you’ll see this difficulty reflected in a stream implementation’s internal complexity. Fortunately, a well-designed stream class hides much of this complexity from users.

Stream Classes

All STL stream classes that users interact with derive from basic_istream, basic_ostream, or both via basic_iostream. The headers that declare each type also provide char and wchar_t specializations for those templates, as outlined in Table 16-1. These heavily used specializations are particularly useful when you’re working with human-language data input and output.

Table 16-1: Template Specializations for the Primary Stream Templates

Template

Parameter

Specialization

Header

basic_istream

char

istream

<istream>

basic_ostream

char

ostream

<ostream>

basic_iostream

char

iostream

<iostream>

basic_istream

wchar_t

wistream

<istream>

basic_ostream

wchar_t

wostream

<ostream>

basic_iostream

wchar_t

wiostream

<iostream>

The objects in Table 16-1 are abstractions that you can use in your programs to write generic code. Do you want to write a function that logs output to an arbitrary source? If so, you can accept an ostream reference parameter and not deal with all the nasty implementation details. (Later in the “Output File Streams” on page 542, you’ll learn how to do this.)

Often, you’ll want to perform I/O with the user (or the program’s environment). Global stream objects provide a convenient, stream-based wrapper for you to work against.

Global Stream Objects

The STL provides several global stream objects in the <iostream> header that wrap the input, output, and error streams stdin, stdout, and stderr. These implementation-defined standard streams are preconnected channels between your program and its executing environment. For example, in a desktop environment, stdin typically binds to the keyboard and stdout and stderr bind to the console.

NOTE

Recall that in Part I you saw extensive use of printf to write to stdout.

Table 16-2 lists the global stream objects, all of which reside in the std namespace.

Table 16-2: The Global Stream Objects

Object

Type

Purpose

cout

wcout

ostream

wostream

Output, like a screen

cin

wcin

istream

wistream

Input, like a keyboard

cerr

wcerr

ostream

wostream

Error output (unbuffered)

clog

wclog

ostream

wostream

Error output (buffered)

So how do you use these objects? Well, stream classes support operations that you can partition into two categories:

Formatted operations Might perform some preprocessing on their input parameters before performing I/O

Unformatted operations Perform I/O directly

The following sections explain each of these categories in turn.

Formatted Operations

All formatted I/O passes through two functions: the standard stream operators, operator<< and operator>>. You’ll recognize these as the left and right shift operators from “Logical Operators” on page 182. Somewhat confusingly, streams overload the left and right shift operators with completely unrelated functionality. The semantic meaning of the expression i << 5 depends entirely on the type of i. If i is an integral type, this expression means take i and shift the bits to the left by five binary digits. If i is not an integral type, it means write the value 5 into i. Although this notational collision is unfortunate, in practice it doesn’t cause too much trouble. Just pay attention to the types you’re using and test your code well.

Output streams overload operator<<, which is referred to as the output operator or the inserter. The basic_ostream class template overloads the output operator for all fundamental types (except void and nullptr_t) and some STL containers, such as basic_string, complex, and bitset. As an ostream user, you need not worry about how these overloads translate objects into readable output.

Listing 16-1 illustrates how to use the output operator to write various types into cout.

#include <iostream>
#include <string>
#include <bitset>

using namespace std;

int main() {
  bitset<8> s{ "01110011" };
  string str("Crying zeros and I'm hearing ");
  size_t num{ 111 };
  cout << s; 
  cout << '
'; 
  cout << str; 
  cout << num; 
  cout << "s
"; 
}
-----------------------------------------------------------------------
01110011 
Crying zeros and I'm hearing 111s 

Listing 16-1: Using cout and operator<< to write into stdout

You use the output operator<< to write a bitset , a char , a string , a size_t , and a null-terminated string literal to stdout via cout. Even though you write five distinct types to the console, you never deal with serialization issues. (Consider the hoops you would have had to jump through to get printf to yield similar output given these types.)

One very nice feature of the standard stream operators is that they generally return a reference to the stream. Conceptually, overloads are typically defined along the following lines:

ostream& operator<<(ostream&, char);

This means you can chain output operators together. Using this technique, you can refactor Listing 16-1 so cout appears only once, as Listing 16-2 illustrates.

#include <iostream>
#include <string>
#include <bitset>

using namespace std;

int main() {
  bitset<8> s{ "01110011" };
  string str("Crying zeros and I'm hearing ");
  size_t num{ 111 };
  cout << s << '
' << str << num << "s
"; 
}
-----------------------------------------------------------------------
01110011
Crying zeros and I'm hearing 111s 

Listing 16-2: Refactoring Listing 16-1 by chaining output operators together

Because each invocation of operator<< returns a reference to the output stream (here, cout), you simply chain the calls together to obtain identical output .

Input streams overload operator>>, which is referred to as the input operator or the extractor. The basic_istream class has corresponding overloads for the input operator for all the same types as basic_ostream, and again as a user, you can largely ignore the deserialization details.

Listing 16-3 illustrates how to use the input operator to read two double objects and a string from cin, then print the implied mathematical operation’s result to stdout.

#include <iostream>
#include <string>

using namespace std;

int main() {
  double x, y;
  cout << "X: ";
  cin >> x; 
  cout << "Y: ";
  cin >> y; 

  string op;
  cout << "Operation: ";
  cin >> op; 
  if (op == "+") {
    cout << x + y; 
  } else if (op == "-") {
    cout << x - y; 
  } else if (op == "*") {
    cout << x * y; 
  } else if (op == "/") {
    cout << x / y; 
  } else {
    cout << "Unknown operation " << op; 
  }
}

Listing 16-3: A primitive calculator program using cin and operator<< to collect input

Here, you collect two doubles x and y followed by the string op , which encodes the desired operation. Using an if statement, you can output the specified operation’s result for addition , subtraction , multiplication , and division , or indicate to the user that op is unknown .

To use the program, you type the requested values into the console when directed. A newline will send the input (as stdin) to cin, as Listing 16-4 illustrates.

X: 3959 
Y: 6.283185 
Operation: * 
24875.1 

Listing 16-4: A sample run of the program in Listing 16-3 that calculates the circumference of Earth in miles

You input the two double objects: the radius of Earth in miles, 3959 and 2π, 6.283185 , and you specify multiplication * . The result is Earth’s circumference in miles . Note that you don’t need to provide a decimal point for an integral value ; the stream is smart enough to know that there’s an implicit decimal.

NOTE

You might wonder what happens in Listing 16-4 if you input a non-numeric string for X or Y . The stream enters an error state, which you’ll learn about later in this chapter in the “Stream State” section on page 530. In an error state, the stream ceases to accept input, and the program won’t accept any more input.

Unformatted Operations

When you’re working with text-based streams, you’ll usually want to use formatted operators; however, if you’re working with binary data or if you’re writing code that needs low-level access to streams, you’ll want to know about the unformatted operations. Unformatted I/O involves a lot of detail. For brevity, this section provides a summary of the relevant methods, so if you need to use unformatted operations, refer to [input.output].

The istream class has many unformatted input methods. These methods manipulate streams at the byte level and are summarized in Table 16-3. In this table, is is of type std::istream <T>, s is a char*, n is a stream size, pos is a position type, and d is a delimiter of type T.

Table 16-3: Unformatted Read Operations for istream

Method

Description

is.get([c])

Returns next character or writes to character reference c if provided.

is.get(s, n, [d])

is.getline(s, n, [d])

The operation get reads up to n characters into the buffer s, stopping if it encounters a newline, or d if provided. The operation getline is the same except it reads the newline character as well. Both write a terminating null character to s. You must ensure s has enough space.

is.read(s, n)

is.readsome(s, n)

The operation read reads up to n characters into the buffer s; encountering end of file is an error. The operation readsome is the same except it doesn’t consider end of file an error.

is.gcount()

Returns the number of characters read by is’s last unformatted read operation.

is.ignore()

Extracts and discards a single character.

is.ignore(n, [d])

Extracts and discards up to n characters. If d is provided, ignore stops if d is found.

is.peek()

Returns the next character to be read without extracting.

is.unget()

Puts the last extracted character back into the string.

is.putback(c)

If c is the last character extracted, executes unget. Otherwise, sets the badbit. Explained in the “Stream State” section.

Output streams have corollary unformatted write operations, which manipulate streams at a very low level, as summarized in Table 16-4. In this table, os is of type std::ostream <T>, s is a char*, and n is a stream size.

Table 16-4: Unformatted Write Operations for ostream

Method

Description

os.put(c)

Writes c to the stream

os.write(s, n)

Writes n characters from s to the stream

os.flush()

Writes all buffered data to the underlying device

Special Formatting for Fundamental Types

All fundamental types, in addition to void and nullptr, have input and output operator overloads, but some have special rules:

char and wchar_t The input operator skips whitespace when assigning character types.

char* and wchar_t* The input operator first skips whitespace and then reads the string until it encounters another whitespace or an end-of-file (EOF). You must reserve enough space for the input.

void* Address formats are implementation dependent for input and output operators. On desktop systems, addresses take hexadecimal literal form, such as 0x01234567 for 32-bit or 0x0123456789abcdef for 64-bit.

bool The input and output operators treat Boolean values as numbers: 1 for true and 0 for false.

Numeric types The input operator requires that input begin with at least one digit. Badly formed input numbers yield a zero-valued result.

These rules might seem a bit strange at first, but they’re fairly straightforward once you get used to them.

NOTE

Avoid reading into C-style strings, because it’s up to you to ensure that you’ve allocated enough space for the input data. Failure to perform adequate checking results in undefined behavior and possibly major security vulnerabilities. Use std::string instead.

Stream State

A stream’s state indicates whether I/O failed. Each stream type exposes the constant static members referred to collectively as its bits, which indicate a possible stream state: goodbit, badbit, eofbit, and failbit. To determine whether a stream is in a particular state, you invoke member functions that return a bool indicating whether the stream is in the corresponding state. Table 16-5 lists these member functions, the stream state corresponding to a true result, and the state’s meaning.

Table 16-5: The Possible Stream States, Their Accessor Methods, and Their Meanings

Method

State

Meaning

good()

goodbit

The stream is in a good working state.

eof()

eofbit

The stream encountered an EOF.

fail()

failbit

An input or output operation failed, but the stream might still be in a good working state.

bad()

badbit

A catastrophic error occurred, and the stream is not in a good state.

NOTE

To reset a stream’s status to indicate a good working state, you can invoke its clear() method.

Streams implement an implicit bool conversion (operator bool), so you can check whether a stream is in a good working state simply and directly. For example, you can read input from stdin word by word until it encounters an EOF (or some other failure condition) using a simple while loop. Listing 16-5 illustrates a simple program that uses this technique to generate word counts from stdin.

#include <iostream>
#include <string>

int main() {
  std::string word; 
  size_t count{}; 
  while (std::cin >> word) 
    count++; 
  std::cout << "Discovered " << count << " words.
"; 
}

Listing 16-5: A program that counts words from stdin

You declare a string called word to receive words from stdin , and you initialize a count variable to zero . Within the while loop’s Boolean expression, you attempt to assign new input into word . When this succeeds, you increment count . Once it fails—for example, due to encountering an EOF—you cease incrementing and print the final tally .

You can try two methods to test Listing 16-5. First, you can simply invoke the program, enter some input, and provide an EOF. How to send EOF depends on your operating system. In the Windows command line, you can enter EOF by pressing CTRL-Z and pressing enter. In Linux bash or in the OS X shell, you press CTRL-D. Listing 16-6 demonstrates how to invoke Listing 16-5 from the Windows command line.

$ listing_16_5.exe 
Size matters not. Look at me. Judge me by my size, do you? Hmm? Hmm. And well
you should not. For my ally is the Force, and a powerful ally it is. Life
creates it, makes it grow. Its energy surrounds us and binds us. Luminous
beings are we, not this crude matter. You must feel the Force around you;
here, between you, me, the tree, the rock, everywhere, yes. 
^Z 
Discovered 70 words. 

Listing 16-6: Invoking the program in Listing 16-5 by typing input into the console

First, you invoke your program . Next, enter some arbitrary text followed by a new line . Then issue EOF. The Windows command line shows the somewhat cryptic sequence ^Z on the command line, after which you must press ENTER. This causes std::cin to enter the eofbit state, ending the while loop in Listing 16-5 . The program indicates that you’ve sent 70 words into stdin .

On Linux and Mac and in Windows PowerShell, you have another option. Rather than entering the input directly into the console, you can save the text to a file, say yoda.txt. The trick is to use cat to read the text file and then use the pipe operator | to send the contents to your program. The pipe operator “pipes” the stdout of the program to its left into the stdin of the program on the right. The following command illustrates this process:

$ cat yoda.txt | ./listing_15_4
Discovered 70 words.

The cat command reads the contents of yoda.txt . The pipe operator pipes the stdout of cat into stdin of listing_15_4 . Because cat sends EOF when it encounters the end of yoda.txt, you don’t need to enter it manually.

Sometimes you’ll want streams to throw an exception when certain fail bits occur. You can do this easily with a stream’s exceptions method, which accepts a single argument corresponding to the bit you want to throw exceptions. If you desire multiple bits, you can simply join them together using Boolean OR (|).

Listing 16-7 illustrates how to refactor Listing 16-5 so it handles the badbit with exceptions and eofbit/failbit with the default handling.

#include <iostream>
#include <string>

using namespace std;

int main() {
  cin.exceptions(istream::badbit); 
  string word;
  size_t count{};
  try { 
    while(cin >> word) 
      count++;
    cout << "Discovered " << count << " words.
"; 
  } catch (const std::exception& e) { 
    cerr << "Error occurred reading from stdin: " << e.what(); 
  }
}

Listing 16-7: Refactoring Listing 16-5 to handle badbit with exceptions

You start the program by invoking the exceptions method on std::cin . Because cin is an istream, you pass istream::badbit as the argument of exception, indicating that you want cin to throw an exception any time it gets into a catastrophic state. To account for possible exceptions, you wrap the existing code in a try-catch block , so if cin sets badbit while it’s reading input , the user never receives a message about the word count . Instead, the program catches the resulting exception and prints the error message .

Buffering and Flushing

Many ostream class templates involve operating system calls under the hood, for example, to write to a console, a file, or a network socket. Relative to other function calls, system calls are usually slow. Rather than invoking a system call for each output element, an application can wait for multiple elements and then send them all together to improve performance.

The queuing behavior is called buffering. When the stream empties the buffered output, it’s called flushing. Usually, this behavior is completely transparent to the user, but sometimes you want to manually flush the ostream. For this (and other tasks), you turn to manipulators.

Manipulators

Manipulators are special objects that modify how streams interpret input or format output. Manipulators exist to perform many kinds of stream alterations. For example, std::ws modifies an istream to skip over whitespace. Here are some other manipulators that work on ostreams:

  • std::flush empties any buffered output directly to an ostream.
  • std::ends sends a null byte.
  • std::endl is like std::flush except it sends a newline before flushing.

Table 16-6 summarizes the manipulators in the <istream> and <ostream> headers.

Table 16-6: Four Manipulators in the <istream> and <ostream> Headers

Manipulator

Class

Behavior

ws

istream

Skips over all whitespaces

flush

ostream

Writes any buffered data to the stream by invoking its flush method

ends

ostream

Sends a null byte

endl

ostream

Sends a newline and flushes

For example, you could replace in Listing 16-7 with the following:

cout << "Discovered " << count << " words." << endl;

This will print a newline and also flush output.

NOTE

As a general rule, use std::endl when your program has finished outputting text to the stream for a while and when you know your program will output more text soon.

The stdlib provides many other manipulators in the <ios> header. You can, for example, determine whether an ostream will represent Boolean values textually (boolalpha) or numerically (noboolalpha); integral values as octal (oct), decimal (dec), or hexadecimal (hex); and floating-point numbers as decimal notation (fixed) or scientific notation (scientific). Simply pass one of these manipulators to an ostream using operator<< and all subsequent insertions of the corresponding type will be manipulated (not just an immediately preceding operand).

You can also set a stream’s width parameter using the setw manipulator. A stream’s width parameter has varied effects, depending on the stream. For example, with std::cout, setw will fix the number of output characters allocated to the next output object. Additionally, for floating-point output, setprecision will set the following numbers’ precision.

Listing 16-8 illustrates how these manipulators perform functions similar to those of the various printf format specifiers.

#include <iostream>
#include <iomanip>

using namespace std;

int main() {
  cout << "Gotham needs its " << boolalpha << true << " hero."; 
  cout << "
Mark it " << noboolalpha << false << "!"; 
  cout << "
There are " << 69 << "," << oct << 105 << " leaves in here."; 
  cout << "
Yabba " << hex << 3669732608 << "!"; 
  cout << "
Avogadro's number: " << scientific << 6.0221415e-23; 
  cout << "
the Hogwarts platform: " << fixed << setprecision(2) << 9.750123; 
  cout << "
Always eliminate " << 3735929054; 
  cout << setw(4) << "
"
       << 0x1 << "
"
       << 0x10 << "
"
       << 0x100 << "
"
       << 0x1000 << endl; 
}
-----------------------------------------------------------------------
Gotham needs its true hero. 
Mark it 0! 
There are 69,151 leaves in here. 
Yabba dabbad00! 
Avogadro's Number: 6.022142e-23 
the Hogwarts platform: 9.75 
Always eliminate deadc0de 
1
10
100
1000 

Listing 16-8: A program illustrating some of the manipulators available in the <iomanip> header

The boolalpha manipulator in the first line causes Boolean values to print textually as true and false , whereas noboolalpha causes them to print as 1 and 0 instead . For integral values, you can print as octal with oct or hexadecimal with hex . For floating-point values, you can specify scientific notation with scientific , and you can set the number of digits to print with setprecision and specify decimal notation with fixed . Because manipulators apply to all subsequent objects you insert into a stream, when you print another integral value at the end of the program, the last integral manipulator (hex) applies, so you get a hexadecimal representation . Finally, you employ setw to set the field width for output to 4, and you print some integral values .

Table 16-7 summarizes this sampling of common manipulators.

Table 16-7: Many of the Manipulators Available in the <iomanip> Header

Manipulator

Behavior

boolalpha

Represents Booleans textually rather than numerically.

noboolalpha

Represents Booleans numerically rather than textually.

oct

Represents integral values as octal.

dec

Represents integral values as decimal.

hex

Represents integral values as hexadecimal.

setw(n)

Sets the width parameter of a stream to n. The exact effect depends on the stream.

setprecision(p)

Specifies floating-point precision as p.

fixed

Represents floating-point numbers in decimal notation.

scientific

Represents floating-point numbers in scientific notation.

NOTE

Refer to Chapter 15 in The C++ Standard Library, 2nd Edition, by Nicolai M. Josuttis or [iostream.format].

User-Defined Types

You can make user-defined types work with streams by implementing certain non-member functions. To implement the output operator for type YourType, the following function declaration serves most purposes:

ostream& operator<<(ostream& s, const YourType& m );

For most cases, you’ll simply return the same ostream you receive . It’s up to you how to send output into the ostream. But typically, this involves accessing fields on YourType , optionally performing some formatting and transformations, and then using the output operator. For example, Listing 16-9 shows how to implement an output operator for std::vector to print its size, capacity, and elements.

#include <iostream>
#include <vector>
#include <string>

using namespace std;

template <typename T>
ostream& operator<<(ostream& s, vector<T> v) { 
  s << "Size: " << v.size()
    << "
Capacity: " << v.capacity()
    << "
Elements:
"; 
  for (const auto& element : v)
    s << "	" << element << "
"; 
  return s; 
}

int main() {
  const vector<string> characters {
    "Bobby Shaftoe",
    "Lawrence Waterhouse",
    "Gunter Bischoff",
    "Earl Comstock"
  }; 
  cout << characters << endl; 

  const vector<bool> bits { true, false, true, false }; 
  cout << boolalpha << bits << endl; 
}
-----------------------------------------------------------------------
Size: 4
Capacity: 4
Elements: 
        Bobby Shaftoe 
        Lawrence Waterhouse 
        Gunter Bischoff 
        Earl Comstock 

Size: 4
Capacity: 32
Elements: 
        true 
        false 
        true 
        false 

Listing 16-9: A program illustrating how to implement an output operator for a vector

First, you define a custom output operator as a template, using the template parameter as the template parameter of std::vector . This allows you to use the output operator for many kinds of vectors (as long as the type T also supports the output operator). The first three lines of output give the size and capacity of vector, as well as the title Elements indicating that the elements of the vector follow . The following for loop iterates over each element in the vector, sending each on a separate line to the ostream . Finally, you return the stream reference s .

Within main, you initialize a vector called characters containing four strings . Thanks to your user-defined output operator, you can simply send characters to cout as if it were a fundamental type . The second example uses a vector<bool> called bits, which you also initialize with four elements and print to stdout . Notice that you use the boolalpha manipulator, so when your user-defined output operator runs, the bool elements print textually .

You can also provide user-defined input operators, which work similarly. A simple corollary is as follows:

istream& operator>>(istream& s, YourType& m );

As with the output operator, the input operator typically returns the same stream it receives . However, unlike with the output operator, the YourType reference will generally not be const, because you’ll want to modify the corresponding object using input from the stream .

Listing 16-10 illustrates how to specify an input operator for deque so it pushes elements into the container until an insertion fails (for example, due to an EOF character).

#include <iostream>
#include <deque>

using namespace std;

template <typename T>
istream& operator>>(istream& s, deque<T>& t) { 
  T element; 
  while (s >> element) 
    t.emplace_back(move(element)); 
  return s; 
}

int main() {
  cout << "Give me numbers: "; 
  deque<int> numbers;
  cin >> numbers; 
  int sum{};
  cout << "Cumulative sum:
";
  for(const auto& element : numbers) {
    sum += element;
    cout << sum << "
"; 
  }
}
-----------------------------------------------------------------------
Give me numbers:  1 2 3 4 5 
Cumulative sum:
1  
3  
6  
10 
15 

Listing 16-10: A program illustrating how to implement an input operator for a deque

Your user-defined input operator is a function template so you can accept any deque containing a type that supports the input operator . First, you construct an element of type T so you can store input from the istream . Next, you use the familiar while construct to accept input from the istream until the input operation fails . (Recall from the “Stream State” section that streams can get into failed states in many ways, including reaching an EOF or encountering an I/O error.) After each insertion, you move the result into emplace_back on the deque to avoid unnecessary copies . Once you’re done inserting, you simply return the istream reference .

Within main, you prompt the user for numbers and then use the insertion operator on a newly initialized deque to insert elements from stdin. In this sample program run, you input the numbers 1 to 5 . For a bit of fun, you compute a cumulative sum by keeping a tally and iterating over each element, printing that iteration’s result .

NOTE

The preceding examples are simple user-defined implementations of input and output operators. You might want to elaborate these implementations in production code. For example, the implementations only work with ostream classes, which implies that they won’t work with any non-char sequences.

String Streams

The string stream classes provide facilities for reading from and writing to character sequences. These classes are useful in several situations. Input strings are especially useful if you want to parse string data into types. Because you can use the input operator, all the standard manipulator facilities are available to you. Output strings are excellent for building up strings from variable-length input.

Output String Streams

Output string streams provide output-stream semantics for character sequences, and they all derive from the class template std::basic_ostringstream in the <sstream> header, which provides the following specializations:

using ostringstream = basic_ostringstream<char>;
using wostringstream = basic_ostringstream<wchar_t>;

The output string streams support all the same features as an ostream. Whenever you send input to the string stream, the stream stores this input into an internal buffer. You can think of this as functionally equivalent to the append operation of string (except that string streams are potentially more efficient).

Output string streams also support the str() method, which has two modes of operation. Given no argument, str returns a copy of the internal buffer as a basic_string (so ostringstream returns a string; wostringstream returns a wstring). Given a single basic_string argument, the string stream will replace its buffer’s current contents with the contents of the argument. Listing 16-11 illustrates how to use an ostringstream, send character data to it, build a string, reset its contents, and repeat.

#include <string>
#include <sstream>

TEST_CASE("ostringstream produces strings with str") {
  std::ostringstream ss; 
  ss << "By Grabthar's hammer, ";
  ss << "by the suns of Worvan. ";

  ss << "You shall be avenged."; 
  const auto lazarus = ss.str(); 

  ss.str("I am Groot."); 
  const auto groot = ss.str(); 

  REQUIRE(lazarus == "By Grabthar's hammer, by the suns"
                     " of Worvan. You shall be avenged.");
  REQUIRE(groot == "I am Groot.");
}

Listing 16-11: Using an ostringstream to build strings

After declaring an ostringstream , you treat it just like any other ostream and use the output operator to send it three separate character sequences . Next, you invoke str without an argument, which produces a string called lazarus . Then you invoke str with the string literal I am Groot , which replaces the contents of ostringstream .

NOTE

Recall from “C-Style Strings” on page 45 that you can place multiple string literals on consecutive lines and the compiler will treat them as one. This is done purely for source code–formatting purposes.

Input String Streams

Input string streams provide input stream semantics for character sequences, and they all derive from the class template std::basic_istringstream in the <sstream> header, which provides the following specializations:

using istringstream = basic_istringstream<char>;
using wistringstream = basic_istringstream<wchar_t>;

These are analogous to the basic_ostringstream specializations. You can construct input string streams by passing a basic_string with appropriate specialization (string for an istringstream and wstring for a wistringstream). Listing 16-12 illustrates by constructing an input string stream with a string containing three numbers and using the input operator to extract them. (Recall from “Formatted Operations” on page 525 that whitespace is the appropriate delimiter for string data.)

TEST_CASE("istringstream supports construction from a string") {
  std::string numbers("1 2.23606 2"); 
  std::istringstream ss{ numbers }; 
  int a;
  float b, c, d;
  ss >> a; 
  ss >> b; 
  ss >> c;
  REQUIRE(a == 1);
  REQUIRE(b == Approx(2.23606));
  REQUIRE(c == Approx(2));
  REQUIRE_FALSE(ss >> d); 
}

Listing 16-12: Using a string to build istringstream objects and extract numeric types

You construct a string from the literal 1 2.23606 2 , which you pass into the constructor of an istringstream called ss . This allows you to use the input operator to parse out int objects and float objects just like any other input stream. Once you’ve exhausted the stream and the output operator fails, ss converts to false .

String Streams Supporting Input and Output

Additionally, if you want a string stream that supports input and output operations, you can use the basic_stringstream, which has the following specializations:

using stringstream = basic_stringstream<char>;
using wstringstream = basic_stringstream<wchar_t>;

This class supports the input and output operators, the str method, and construction from a string. Listing 16-13 illustrates how to use a combination of input and output operators to extract tokens from a string.

TEST_CASE("stringstream supports all string stream operations") {
  std::stringstream ss;
  ss << "Zed's DEAD"; 

  std::string who;
  ss >> who; 
  int what;
  ss >> std::hex >> what; 

  REQUIRE(who == "Zed's");
  REQUIRE(what == 0xdead);
}

Listing 16-13: Using a stringstream for input and output

You create a stringstream and sent the Zed's DEAD with the output operator . Next, you parse Zed's out of the stringstream using the input operator . Because DEAD is a valid hexadecimal integer, you use the input operator and the std::hex manipulator to extract it into an int .

NOTE

All string streams are moveable.

Summary of String Stream Operations

Table 16-8 provides a partial list of basic_stringstream operations. In this table, ss, ss1, and ss2 are of type std::basic_stringstream<T>; s is a std::basic_string<T>; obj is a formatted object; pos is a position type; dir is a std::ios_base::seekdir; and flg is a std::ios_base::iostate.

Table 16-8: A Partial List of std::basic_stringstream Operations

Operation

Notes

basic_stringstream<T>

{ [s], [om] }

Performs braced initialization of a newly constructed string stream. Defaults to empty string s and in|out open mode om.

basic_stringstream<T>

{ move(ss) }

Takes ownership of ss’s internal buffer.

~basic_stringstream

Destructs internal buffer.

ss.rdbuf()

Returns raw string device object.

ss.str()

Gets the contents of the string device object.

ss.str(s)

Sets the contents of the string device object to s.

ss >> obj

Extracts formatted data from the string stream.

ss << obj

Inserts formatted data into the string stream.

ss.tellg()

Returns the input position index.

ss.seekg(pos)

ss.seekg(pos, dir)

Sets the input position indicator.

ss.flush()

Synchronizes the underlying device.

ss.good()

ss.eof()

ss.bad()

!ss

Inspects the string stream’s bits.

ss.exceptions(flg)

Configures the string stream to throw an exception whenever a bit in flg gets set.

ss1.swap(ss2)

swap(ss1, ss2)

Exchanges each element of ss1 with those of ss2.

File Streams

The file stream classes provide facilities for reading from and writing to character sequences. The file stream class structure follows that of the string stream classes. File stream class templates are available for input, output, and both.

File stream classes provide the following major benefits over using native system calls to interact with file contents:

  • You get the usual stream interfaces, which provide a rich set of features for formatting and manipulating output.
  • The file stream classes are RAII wrappers around the files, meaning it’s impossible to leak resources, such as files.
  • File stream classes support move semantics, so you can have tight control over where files are in scope.
Opening Files with Streams

You have two options for opening a file with any file stream. The first option is the open method, which accepts a const char* filename and an optional std::ios_base::openmode bitmask argument. The openmode argument can be one of the many possible combinations of values listed in Table 16-9.

Table 16-9: Possible Stream States, Their Accessor Methods, and Their Meanings

Flag (in std::ios)

File

Meaning

in

Must exist

Read

out

Created if doesn’t exist

Erase the file; then write

app

Created if doesn’t exist

Append

in|out

Must exist

Read and write from beginning

in|app

Created if doesn’t exist

Update at end

out|app

Created if doesn’t exist

Append

out|trunc

Created if doesn’t exist

Erase the file; then read and write

in|out|app

Created if doesn’t exist

Update at end

in|out|trunc

Created if doesn’t exist

Erase the file; then read and write

Additionally, you can add the binary flag to any of these combinations to put the file in binary mode. In binary mode, the stream won’t convert special character sequences, like end of line (for example, a carriage return plus a line feed on Windows) or EOF.

The second option for specifying a file to open is to use the stream’s constructor. Each file stream provides a constructor taking the same arguments as the open method. All file stream classes are RAII wrappers around the file handles they own, so the files will be automatically cleaned up when the file stream destructs. You can also manually invoke the close method, which takes no arguments. You might want to do this if you know you’re done with the file but your code is written in such a way that the file stream class object won’t destruct for a while.

File streams also have default constructors, which don’t open any files. To check whether a file is open, invoke the is_open method, which takes no arguments and returns a Boolean.

Output File Streams

Output file streams provide output stream semantics for character sequences, and they all derive from the class template std::basic_ofstream in the <fstream> header, which provides the following specializations:

using ofstream = basic_ofstream<char>;
using wofstream = basic_ofstream<wchar_t>;

The default basic_ofstream constructor doesn’t open a file, and the non-default constructor’s second optional argument defaults to ios::out.

Whenever you send input to the file stream, the stream writes the data to the corresponding file. Listing 16-14 illustrates how to use ofstream to write a simple message to a text file.

#include <fstream>

using namespace std;

int main() {
  ofstream file{ "lunchtime.txt", ios::out|ios::app }; 
  file << "Time is an illusion." << endl; 
  file << "Lunch time, " << 2 << "x so." << endl; 
}
-----------------------------------------------------------------------
lunchtime.txt:
Time is an illusion. 
Lunch time, 2x so. 

Listing 16-14: A program opening the file lunchtime.txt and appending a message to it. (The output corresponds to the contents of lunchtime.txt after a single program execution.)

You initialize an ofstream called file with the path lunchtime.txt and the flags out and app . Because this combination of flags appends output, any data you send through the output operator into this file stream gets appended to the end of the file. As expected, the file contains the message you passed to the output operator .

Thanks to the ios::app flag, the program will append output to lunchtime.txt if it exists. For example, if you run the program again, you’ll get the following output:

Time is an illusion.
Lunch time, 2x so.
Time is an illusion.
Lunch time, 2x so.

The second iteration of the program added the same phrase to the end of the file.

Input File Streams

Input file streams provide input stream semantics for character sequences, and they all derive from the class template std::basic_ifstream in the <fstream> header, which provides the following specializations:

using ifstream = basic_ifstream<char>;
using wifstream = basic_ifstream<wchar_t>;

The default basic_ifstream constructor doesn’t open a file, and the non-default constructor’s second optional argument defaults to ios::in.

Whenever you read from the file stream, the stream reads data from the corresponding file. Consider the following sample file, numbers.txt:

-54
203
9000
0
99
-789
400

Listing 16-15 contains a program that uses an ifstream to read from a text file containing integers and return the maximum. The output corresponds with invoking the program and passing the path of the file numbers.txt.

#include <iostream>
#include <fstream>
#include <limits>

using namespace std;

int main() {
  ifstream file{ "numbers.txt" }; 
  auto maximum = numeric_limits<int>::min(); 
  int value;
  while (file >> value) 
    maximum = maximum < value ? value : maximum; 
  cout << "Maximum found was " << maximum << endl; 
}
-----------------------------------------------------------------------
Maximum found was 9000 

Listing 16-15: A program that reads the text file numbers.txt and prints its maximum integer

You first initialize an istream to open the numbers.txt text file . Next, you initialize the maximum variable with the minimum value an int can take . Using the idiomatic input stream and while-loop combination , you cycle through each integer in the file, updating the maximum as you find higher values . Once the file stream cannot parse any more integers, you print the result to stdout .

Handling Failure

As with other streams, file streams fail silently. If you use a file stream constructor to open a file, you must check the is_open method to determine whether the stream successfully opened the file. This design differs from most other stdlib objects where invariants are enforced by exceptions. It’s hard to say why the library implementors chose this approach, but the fact is that you can opt into an exception-based approach fairly easily.

You can make your own factory functions to handle file-opening failures with exceptions. Listing 16-16 illustrates how to implement an ifstream factory called open.

#include <fstream>
#include <string>

using namespace std;

ifstream open(const char* path, ios_base::openmode mode = ios_base::in) {
  ifstream file{ path, mode }; 
  if(!file.is_open()) { 
    string err{ "Unable to open file " };
    err.append(path);
    throw runtime_error{ err }; 
  }
  file.exceptions(ifstream::badbit);
  return file; 
}

Listing 16-16: A factory function for generating ifstreams that handle errors with exceptions rather than failing silently

Your factory function returns an ifstream and accepts the same arguments as a file stream’s constructor (and open method): a file path and an openmode . You pass these two arguments into the constructor of ifstream and then determine whether the file opened successfully . If it didn’t, you throw a runtime_error . If it did, you tell the resulting ifstream to throw an exception whenever its badbit gets set in the future .

Summary of File Stream Operations

Table 16-10 provides a partial list of basic_fstream operations. In this table, fs, fs1, and fs2 are of type std:: basic_fstream <T>; p is a C-style string, std::string, or a std::filesystem::path; om is an std::ios_base::openmode; s is a std::basic_string<T>; obj is a formatted object; pos is a position type; dir is a std::ios_base::seekdir; and flg is a std::ios_base::iostate.

Table 16-10: A Partial List of std::basic_fstream Operations

Operation

Notes

basic_fstream<T>

{ [p], [om] }

Performs braced initialization of a newly constructed file stream. If p is provided, attempts to open file at path p. Defaults to not opened and in|out open mode.

basic_fstream<T>

{ move(fs) }

Takes ownership of the internal buffer of fs.

~basic_fstream

Destructs internal buffer.

fs.rdbuf()

Returns raw string device object.

fs.str()

Gets the contents of the file device object.

fs.str(s)

Puts the contents of the file device object into s.

fs >> obj

Extracts formatted data from the file stream.

fs << obj

Inserts formatted data into the file stream.

fs.tellg()

Returns the input position index.

fs.seekg(pos)

fs.seekg(pos, dir)

Sets the input position indicator.

fs.flush()

Synchronizes the underlying device.

fs.good()

fs.eof()

fs.bad()

!fs

Inspects the file stream’s bits.

fs.exceptions(flg)

Configures the file stream to throw an exception whenever a bit in flg gets set.

fs1.swap(fs2)

swap(fs1, fs2)

Exchanges each element of fs1 with one of fs2.

Stream Buffers

Streams don’t read and write directly. Under the covers, they use stream buffer classes. At a high level, stream buffer classes are templates that send or extract characters. The implementation details aren’t important unless you’re planning on implementing your own stream library, but it’s important to know that they exist in several contexts. The way you obtain stream buffers is by using a stream’s rdbuf method, which all streams provide.

Writing Files to sdout

Sometimes you just want to write the contents of an input file stream directly into an output stream. To do this, you can extract the stream buffer pointer from the file stream and pass it to the output operator. For example, you can dump the contents of a file to stdout using cout in the following way:

cout << my_ifstream.rdbuf()

It’s that easy.

Output Stream Buffer Iterators

Output stream buffer iterators are template classes that expose an output iterator interface that translates writes into output operations on the underlying stream buffer. In other words, these are adapters that allow you to use output streams as if they were output iterators.

To construct an output stream buffer iterator, use the ostreambuf_iterator template class in the <iterator> header. Its constructor takes a single output stream argument and a single template parameter corresponding to the constructor argument’s template parameter (the character type). Listing 16-17 shows how to construct an output stream buffer iterator from cout.

#include <iostream>
#include <iterator>

using namespace std;

int main() {
  ostreambuf_iterator<char> itr{ cout }; 
  *itr = 'H'; 
  ++itr; 
  *itr = 'i'; 
}
-----------------------------------------------------------------------
Hi

Listing 16-17: Writing the message Hi to stdout using the ostreambuf_iterator class

Here, you construct an output stream buffer iterator from cout , which you write to in the usual way for an output operator: assign , increment , assign , and so on. The result is character-by-character output to stdout. (Recall the procedures for handling output operators in “Output Iterators” on page 464.)

Input Stream Buffer Iterators

Input stream buffer iterators are template classes that expose an input iterator interface that translates reads into read operations on the underlying stream buffer. These are entirely analogous to output stream buffer iterators.

To construct an input stream buffer iterator, use the istreambuf_iterator template class in the <iterator> header. Unlike ostreambuf_iterator, it takes a stream buffer argument, so you must call rdbuf() on whichever input stream you want to adapt. This argument is optional: the default constructor of istreambuf_iterator corresponds to the end-of-range iterator of input iterator. For example, Listing 16-18 illustrates how to construct a string from std::cin using the range-based constructor of string.

#include <iostream>
#include <iterator>
#include <string>

using namespace std;

int main() {
  istreambuf_iterator<char> cin_itr{ cin.rdbuf() } , end{} ;
  cout << "What is your name? "; 
  const string name{ cin_itr, end }; 
  cout << "
Goodbye, " << name; 
}
-----------------------------------------------------------------------
What is your name? josh 
Goodbye, josh

Listing 16-18: Constructing a string from cin using input stream buffer iterators

You construct an istreambuf_iterator from the stream buffer of cin as well as the end-of-range iterator . After sending a prompt to the program’s user , you construct the string name using its range-based constructor . When the user sends input (terminated by EOF), the string’s constructor copies it. You then bid the user farewell using their name . (Recall from “Stream State” on page 530 that methods for sending EOF to the console differ by operating system.)

Random Access

Sometimes you’ll want random access into a stream (especially a file stream). The input and output operators clearly don’t support this use case, so basic_istream and basic_ostream offer separate methods for random access. These methods keep track of the cursor or position, the index of the stream’s current character. The position indicates the next byte that an input stream will read or an output stream will write.

For input streams, you can use the two methods tellg and seekg. The tellg method takes no arguments and returns the position. The seekg method allows you to set the cursor position, and it has two overloads. Your first option is to provide a pos_type position argument, which sets the read position. The second is to provide an off_type offset argument plus an ios_base::seekdir direction argument. The pos_type and off_type are determined by the template arguments to the basic_istream or basic_ostream, but usually these convert to/from integer types. The seekdir type takes one of the following three values:

  • ios_base::beg specifies that the position argument is relative to the beginning.
  • ios_base::cur specifies that the position argument is relative to the current position.
  • ios_base::end specifies that the position argument is relative to the end.

For output streams, you can use the two methods tellp and seekp. These are roughly analogous to the tellg and seekg methods of input streams: the p stands for put and the g stands for get.

Consider a file introspection.txt with the following contents:

The problem with introspection is that it has no end.

Listing 16-19 illustrates how to employ random access methods to reset the file cursor.

#include <fstream>
#include <exception>
#include <iostream>

using namespace std;

ifstream open(const char* path, ios_base::openmode mode = ios_base::in) { 
--snip--
}

int main() {
  try {
    auto intro = open("introspection.txt"); 
    cout << "Contents: " << intro.rdbuf() << endl; 
    intro.seekg(0); 
    cout << "Contents after seekg(0): " << intro.rdbuf() << endl; 
    intro.seekg(-4, ios_base::end); 
    cout << "tellg() after seekg(-4, ios_base::end): "
                                                    << intro.tellg() << endl; 
    cout << "Contents after seekg(-4, ios_base::end): "
                                                    << intro.rdbuf() << endl; 
  }
  catch (const exception& e) {
    cerr << e.what();
  }
}
-----------------------------------------------------------------------
Contents: The problem with introspection is that it has no end. 
Contents after seekg(0): The problem with introspection is that it has no end. 
tellg() after seekg(-4, ios_base::end): 49 
Contents after seekg(-4, ios_base::end): end. 

Listing 16-19: A program using random access methods to read arbitrary characters in a text file

Using the factory function in Listing 16-16 , you open the text file introspection.txt . Next, you print the contents to stdout using the rdbuf method , rewind the cursor to the first character , and print the contents again. Notice that these yield identical output (because the file hasn’t changed) . You then use the relative offset overload of seekg to navigate to the fourth character from the end . Using tellg, you learn that this is the 49th character (with zero-base indexing) . When you print the input file to stdout, the output is only end., because these are the last four characters in the file .

NOTE

Boost offers an IOStream library with a rich set of additional features that stdlib doesn’t have, including facilities for memory mapped file I/O, compression, and filtering.

Summary

In this chapter, you learned about streams, the major concept that provides a common abstraction for performing I/O. You also learned about files as a primary source and destination for I/O. You first learned about the fundamental stream classes in the stdlib and how to perform formatted and unformatted operations, inspect stream state, and handle errors with exceptions. You learned about manipulators and how to incorporate streams into user-defined types, string streams, and file streams. This chapter culminated with stream buffer iterators, which allow you to adapt a stream to an iterator.

EXERCISES

16-1. Implement an output operator that prints information about the AutoBrake from “An Extended Example: Taking a Brake” on page 283. Include the vehicle’s current collision threshold and speed.

16-2. Write a program that takes output from stdin, capitalizes it, and writes the result to stdout.

16-3. Read the introductory documentation for Boost IOStream.

16-4. Write a program that accepts a file path, opens the file, and prints summary information about the contents, including word count, average word length, and a histogram of the characters.

FURTHER READING

  • Standard C++ IOStreams and Locales: Advanced Programmer’s Guide and Reference by Angelika Langer (Addison-Wesley Professional, 2000)
  • ISO International Standard ISO/IEC (2017) — Programming Language C++ (International Organization for Standardization; Geneva, Switzerland; https://isocpp.org/std/the-standard/)
  • The Boost C++ Libraries, 2nd Edition, by Boris Schäling (XML Press, 2014)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset