Skip to content

Chapter 10: Input and Output

10.1

  • An ostream converts typed objects to a stream of characters (bytes).
  • An istream converts a stream of characters (bytes) to typed objects.
ostream: typed value -> ostream -> stream buffer -> byte sequence
istream: typed value <- ostream <- stream buffer <- byte sequence

The streams can be used for input into and output from std::strings, for formatting into string buffers.

10.2

In <ostream>, the I/O stream library defines output for every built-in type. Further, it is easy to define output of a user-defined type. The operator << is used as an output operator on objects of type ostream. std::cout is the standard output stream and std::cerr is the standard stream for reporting errors (unbuffered), and clog is a buffered "logging output". By default, values written to cout are converted to a sequence of characters.

A character constant is a character enclosed in single quotes.

10.3

In <istream>, the standard library offers istreams for input. The operator >> is used as an input operator; std::cin is the standard input stream.

The read of the integer is terminated by any character that is not a digit. By default, >> skips initial whitespace, so a suitable complete input sequence would be 123.4e5.

By default, a whitespace character, such as a space or a newline, terminates the read. You can read a whole line using the std::getline() function.

Using the formatted I/O operations is usually less error-prone, more efficient, and less code than manipulating characteres one by one. In particular, istreams take care of memory management and range checking. We can do formatting to and from memory using stringstreams.

10.4

An iostream has a state that we can examine to determine whether an operation succeeded.

for (int i, istream is; is >> i;){}

This reads from is until something that is not an integer is encountered. That something will typically be the end of input. What is happening here is that the operation is>>i returns a reference to is, and testing an iostream yields true if the stream is ready for another operation.

In general, the I/O state holds all the information needed to read/write, such as formatting and error state, and what kind of buffering is used.

is.clear();
is.unget();
is.eof();
is.fail();
cin.setstate(ios_base::failbit);

10.5

The iostream library allows programmers to define I/O for their own types.

The is>>c skips whitespace by default, but is.get(c) does not.

10.6

The simplest formatting cnotrols are called manipulators, and are found in <ios>, <istream>, <ostream>, <iomanip>(for manipulator that takes arguments.)

std::cout << 1234 << std::hex << 1234 << std::oct << 1234 << '\n';
std::cout <<  d <<  std::scientific << d << std::hexfloat << d << std::fixed << d <<  std::defaultfloat << d << '\n'; 

std::cout.precision(8);
std::cout << 1234.56789 << '\n';
std::cout.precision(4);
std::cout << 1234.56789 << '\n';

Floating-point values are rounded rather than just truncated, and precision() doesn't affect integer output.

11.6.2 printf()-style Formaating from 3rd edition

It has been credibly argued that printf() is the most popular function in C and a significant factor in its success.

printf("an int %g and a string '%s'\n", 123, "Hello!");

This "format string followed by arguments"-style was adopted into C from BCPL and has been followed by many languges.

In <format>, the standard library provides a type-safe printf()-style formatting mechanism. The basic function, format() produces a string

std::string s = std::format("Hello, {}\n", val);

std::cout << std::format("{} {:x} {:o} {:d}\n", 1234, 1234, 1234, 1234);

However, format() offers a non-extensible mini-language of about 60 format specifiers allowing very detailed control over formatting of numbers and dates. If a formatting error is caught at run time, a format_error exception is thrown.

The complimentary function vformat() takes a variable as a format to significantly increase flexiblity and the opportunities for run-timer errors:

std::string fmt = "{}";
std::cout << std::vformat(fmt, std::make_format_args(2));  // OK
fmt = "{:%F}";
std::cout << std::vformat(fmt, std::make_format_args(2));  // ERROR: format and argument mismatch; caught at run time.

Finally, a formatter can also write directly into a buffer defined by an iterator.

std::string buf;
std::format_to(back_inserter(buf), "iterator: {} {}\n", "Hi ", 2022);
std::cout << buf;

This gets interesting for perfromance if we use a stream's buffer directly or the buffer for some other output device.

10.7

In <fstream>, the standard libray provides streams to and from files.

10.8

In <sstream>, the standard library provides streams to and from a std::string.

The result from an ostringstream can be read using str() or view() (a string_view content). One common use of an ostringstream is to format before giving the resulting string to a GUI. Similarly, a string received from a GUI can be read using formatted input operations by putting it into an istringstream.

void test()
{
    std::ostringstream oss;
    oss << "{temperature, " << std::scientific << 123.456 << "}";
    std::cout << oss.view() << '\n';
}

11.7.4 Memory Streams from 3rd edition

spanstream, ispanstream, and ospanstream will not become official before C++23. However, they are already widely available, such as from Github

An ospanstream behaves like an ostringstream and is initialized like it except that the ospanstream takes a span rather than a string as an argument.

void user(int arg)
{
    std::array<char, 128> buf;
    ospanstream ss(buf);
    ss << "write " << arg << " to memory\n";
}

Attempts overflow the target buffer sets the string state to failure

11.7.5 Synchronized Streams from 3rd edition

An osyncstream guarantees that a sequence of output operations will complete and their results will be as expected in the output buffer even if some other thread tries to write.

// different threads calling this function may introduce a data race.
void unsafe(int x, string& s)
{
    cout << x;
    cout << s;
}

void safer(int x, string& s)
{
    osyncstream oss(cout)
    oss << x;
    oss << s;
}

10.9

If you don't use C-style I/O and care about I/O performance, call ios_base::sync_with_stdio(false);. Without that call, iostreams can be significantly slowed down to be compatible with the C-style I/O

10.10

Most systems have a notion of a file system providing access to permanent information stored as files. unfortunately, the properties of file systems and the ways of manipulating them vary greatly. To deal with that, the file system library in <filesystem> offers a uniform interface to most facilities of most file systems. Using <filesystem>, we can portably

  • express file system paths and navigate through a file system
  • examine file types and the permissions associated with them

A path is quite a complicated class, capable of handling the native character sets and conventions of many operating system.In particular, it can handle file names from command lines.

In addition to path, <filesystem> offers types for traversing directories and inquiring about the properties of the files found

API meaning
path A directory path
filesystem_error A file system exception
directory_entry A directory entry
directory_iterator For iterating over a directory
recursive_directory_iterator For iterating over a directory and its subdirectories

Path Operations

API meaning
value_type Character type used by the native encoding of the filesysstem. char on POSIX, wchar_t on Windows
string_type std::basic_string<value_type>
const_iterator A const bidirectional iterator with a value_type of path
iterator Alias for const_iterator
p=p2 assignment
p/=p2 concat with filename separator
p+=p2 concat with no separator
p.native() The native format of p
p.string() p in the native format of p as string
p.generic_string() p in the generic format as a string
p.filename() the filename part of p
p.stem() the stem parat of p
p.extension() the file extension part of p
p.begin() the begining of p's element sequence
p.end() the end of p's element sequence
p==p2, p!=p2 equality comparison
p<=>p2 lexicographical comparison
is>>p, os<<p stream I/O to/from p
u8path(s) A path from a UTF-8 encoded source s

File System Operations

API meaning
exists(p) Does p refer to an existing file system object
copy(p1, p2) copy files or directories from p1 to p2; report errors as exceptions
copy(p1, p2, e) Copy files or directoresl report errors as error codes
b=copy_file(p1,p2) Copy file contents from p1 to p2; error as exception
b=create_directory(p) create new directory named p; all intermediate directories on p must exist
b=create_directories(p) create new directory named p; create all intermediate directories on p
p=current_path() p is the current working directory
current_path(p) Make p the current working directory
s=file_size(p) s is the number of bytes in p
b=remove(p) Remove p if it is a file or an empty directory

We use the error codes when operations are expected to fail frequently in normal use and the throwing operations when an error is considered exceptional

File types

API meaning
is_block_file(f) Is f a block device?
is_character_file(f) Is f a character device?
is_directory(f) Is f a directory?
is_empty(f) Is f an empty file or directory?
is_fifo(f) Is f a named pipe?
is_other(f) Is f some other kind of file?
is_regular_file(f) Is f a regular (ordinary) file?
is_socket(f) Is f a named IPC socket?
is_symlink(f) Is f a symbolic link?
status_known(f) Is f's file status known?

10.11

  • Avoid endl.
  • Unless you use printf-family functions call ios_base::sync_with_stdio(false).