11

Input and Output

What you see is all you get.

– Brian W. Kernighan

11.1 Introduction

The I/O stream library provides formatted and unformatted buffered I/O of text and numeric values. It is extensible to support user-defined types exactly like built-in types and type safe.

The file system library provides basic facilities for manipulating files and directories.

An ostream converts typed objects to a stream of characters (bytes):

Images

An istream converts a stream of characters (bytes) to typed objects:

Images

The operations on istreams and ostreams are described in §11.2 and §11.3. The operations are type-safe, type-sensitive, and extensible to handle user-defined types (§11.5).

Other forms of user interaction, such as graphical I/O, are handled through libraries that are not part of the ISO standard and therefore not described here.

These streams can be used for binary I/O, be used for a variety of character types, be locale specific, and use advanced buffering strategies, but these topics are beyond the scope of this book.

The streams can be used for input into and output from strings (§11.3), for formatting into string buffers (§11.7.3), into areas of memory (§11.7.4), and for file I/O (§11.9).

The I/O stream classes all have destructors that free all resources owned (such as buffers and file handles). That is, they are examples of "Resource Acquisition Is Initialization" (RAII; §6.3).

11.2 Output

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 (§11.5). The operator << (“put to”) is used as an output operator on objects of type ostream; cout is the standard output stream and cerr is the standard stream for reporting errors. By default, values written to cout are converted to a sequence of characters. For example, to output the decimal number 10, we can write:

cout << 10;

This places the character 1 followed by the character 0 on the standard output stream.

Equivalently, we could write:

int x {10};
cout << x;

Output of different types can be combined in the obvious way:

void h(int i)
{
         cout << "the value of i is ";
         cout << i;
         cout << '
';
}

For h(10), the output will be:

the value of i is 10

People soon tire of repeating the name of the output stream when outputting several related items. Fortunately, the result of an output expression can itself be used for further output. For example:

void h2(int i)
{
         cout << "the value of i is " << i << '
';
}

This h2() produces the same output as h().

A character constant is a character enclosed in single quotes. Note that a character is output as a character rather than as a numerical value. For example:

int b = 'b';         //  note: char implicitly converted to int
char c = 'c';
cout << 'a' << b << c;

The integer value of the character ’b’ is 98 (in the ASCII encoding used on the C++ implementation that I used), so this will output a98c.

11.3 Input

In <istream>, the standard library offers istreams for input. Like ostreams, istreams deal with character string representations of built-in types and can easily be extended to cope with user-defined types.

The operator >> (“get from”) is used as an input operator; cin is the standard input stream. The type of the right-hand operand of >> determines what input is accepted and what is the target of the input operation. For example:

int i;
cin >> i;           // read an integer into i

double d;
cin >> d;          // read a double-precision floating-point number into d

This reads a number, such as 1234, from the standard input into the integer variable i and a floating-point number, such as 12.34e5, into the double-precision floating-point variable d.

Like output operations, input operations can be chained, so I could equivalently have written:

int i;
double d;
cin >> i >> d;         // read into i and d

In both cases, 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

1234
12.34e5

Often, we want to read a sequence of characters. A convenient way of doing that is to read into a string. For example:

cout << "Please enter your name
";
string str;
cin >> str;
cout << "Hello, " << str << "!
";

If you type in Eric the response is:

Hello, Eric!

By default, a whitespace character, such as a space or a newline, terminates the read, so if you enter Eric Bloodaxe pretending to be the ill-fated king of York, the response is still:

Hello, Eric!

You can read a whole line using the getline() function. For example:

cout << "Please enter your name
";
string str;
getline(cin,str);
cout << "Hello, " << str << "!
";

With this program, the input Eric Bloodaxe yields the desired output:

Hello, Eric Bloodaxe!

The newline that terminated the line is discarded, so cin is ready for the next input line.

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

The standard strings have the nice property of expanding to hold what you put in them; you don’t have to pre-calculate a maximum size. So, if you enter a couple of megabytes of semicolons, hello_line() will echo pages of semicolons back at you.

11.4 I/O State

An iostream has a state that we can examine to determine whether an operation succeeded. The most common use is to read a sequence of values:

vector<int> read_ints(istream& is)
{
        vector<int> res;
        for (int i; is>>i; )
                 res.push_back(i);
        return res;
}

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 or write, such as formatting information (§11.6.2), error state (e.g., has end-of-input been reached?), and what kind of buffering is used. In particular, a user can set the state to reflect that an error has occurred (§11.5) and clear the state if an error wasn’t serious. For example, we could imagine a version of read_ints() that accepted a terminating string:

vector<int> read_ints(istream& is, const string& terminator)
{
         vector<int> res;
         for (int i; is >> i; )
                  res.push_back(i);

         if (is.eof())                            // fine: end of file
                   return res;
         if (is.fail()) {                         // we failed to read an int; was it the terminator?
                   is.clear();          // reset the state to good()
                   string s;
                   if (is>>s && s==terminator)
                            return res;
                   is.setstate(ios_base::failbit);           // add fail() to is's state
         }
         return res;
}

auto v = read_ints(cin,"stop");

11.5 I/O of User-Defined Types

In addition to the I/O of built-in types and standard strings, the iostream library allows us to define I/O for our own types. For example, consider a simple type Entry that we might use to represent entries in a telephone book:

struct Entry {
         string name;
         int number;
};

We can define a simple output operator to write an Entry using a {"name",number} format similar to the one we use for initialization in code:

ostream& operator<<(ostream& os, const Entry& e)
{
       return os << "{"" << e.name << "", " << e.number << "}";
}

A user-defined output operator takes its output stream (by reference) as its first argument and returns it as its result.

The corresponding input operator is more complicated because it has to check for correct formatting and deal with errors:

istream& operator>>(istream& is, Entry& e)
         // read { "name" , number } pair. Note: formatted with { " " , and }
{
         char c, c2;
         if (is>>c && c=='{' && is>>c2 && c2== " ) { // start with a { followed by a "
                string name;                                // the default value of a string is the empty string: ""
                while (is.get(c) && c!= " )           // anything before a " is part of the name
                         name+=c;

                if (is>>c && c==',') {
                         int number = 0;
                          if (is>>number>>c && c=='}') { // read the number and a }
                                e = {name,number};            // assign to the entry
                                return is;
                          }
                }
         }

         is.setstate(ios_base::failbit);            // register the failure in the stream
         return is;
}

An input operation returns a reference to its istream that can be used to test if the operation succeeded. For example, when used as a condition, is>>c means “Did we succeed in reading a char from is into c?”

The is>>c skips whitespace by default, but is.get(c) does not, so this Entry-input operator ignores (skips) whitespace outside the name string, but not within it. For example:

{ "John Marwood Cleese", 123456       }
{"Michael Edward Palin", 987654}

We can read such a pair of values from input into an Entry like this:

for (Entry ee; cin>>ee; )        // read from cin into ee
         cout << ee <<'
';          // write ee to cout

The output is:

{"John Marwood Cleese", 123456}
{"Michael Edward Palin", 987654}

See §10.4 for a more systematic technique for recognizing patterns in streams of characters (regular expression matching).

11.6 Output Formatting

The iostream and format libraries provide operations for controlling the format of input and output. The iostream facilities are about as old as C++ and focus on formatting streams of numbers. The format facilities (§11.6.2) are recent (C++20) and focus on printf()-style (§11.8) specification of the formatting of combinations of values.

Output formatting also offers support for unicode, but that’s beyond the scope of this book.

11.6.1 Stream Formatting

The simplest formatting controls are called manipulators and are found in <ios>, <istream>, <ostream>, and <iomanip> (for manipulators that take arguments). For example, we can output integers as decimal (the default), octal, or hexadecimal numbers:

cout << 1234 << ' ' << hex << 1234 << ' ' << oct << 1234 << dec << 1234 <<'
';  // 1234 4d2 2322 1234

We can explicitly set the output format for floating-point numbers:

constexpr double d = 123.456;

cout << d << "; "                                   // use the default format for d
        << scientific << d << "; "             // use 1.123e2 style format for d
        << hexfloat << d << "; "               // use hexadecimal notation for d
        << fixed << d << "; "                    // use 123.456 style format for d
        << defaultfloat << d <<'
';         // use the default format for d

This produces:

123.456; 1.234560e+002; 0x1.edd2f2p+6; 123.456000; 123.456

Precision is an integer that determines the number of digits used to display a floating-point number:

  • The general format (defaultfloat) lets the implementation choose a format that presents a value in the style that best preserves the value in the space available. The precision specifies the maximum number of digits.

  • The scientific format (scientific) presents a value with one digit before a decimal point and an exponent. The precision specifies the maximum number of digits after the decimal point.

  • The fixed format (fixed) presents a value as an integer part followed by a decimal point and a fractional part. The precision specifies the maximum number of digits after the decimal point.

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

cout.precision(8);
cout << "precision(8): " << 1234.56789 << ' ' << 1234.56789 << ' ' << 123456 << '
';

cout.precision(4);
cout << "precision(4): " << 1234.56789 << ' ' << 1234.56789 << ' ' << 123456 << '
';
cout << 1234.56789 << '
';

This produces:

precision(8): 1234.5679 1234.5679 123456
precision(4): 1235 1235 123456
1235

These floating-point manipulators are “sticky”; that is, their effects persist for subsequent floating-point operations. That is, they are designed primarily for formatting streams of values.

We can also specify the size of the field that a number is to be placed into and its alignment in that field.

In addition to the basic numbers, << can also handle time and dates: duration, time_point year_month_date, weekday, month, and zoned_time16.2). For example:

cout << "birthday: " << November/28/2021 << '
';
cout << << "zt: " << zoned_time{current_zone(), system_clock::now()} << '
';

This produced:

birthday: 2021-11-28
zt: 2021-12-05 11:03:13.5945638 EST

The standard also defines << for complex numbers, bitsets (§15.3.2), error codes, and pointers. Stream I/O is extensible, so we can define << for our own (user-defined) types (§11.5).

11.6.2 printf()-style Formatting

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

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

This “format string followed by arguments”-style was adopted into C from BCPL and has been followed by many languages. Naturally, printf() has always been part of the C++ standard library, but it suffers from lack of type safety and lack of extensibility to handle user-defined types.

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

string s = format("Hello, {}
", val);

“Ordinary characters” in the format string are simply put into the output string. A format string delimited by { and } specify how arguments following the format string are to be inserted into the string. The simplest format string is the empty string, {}, that takes the next argument from the argument list and prints it according to its << default (if any). So, if val is "World", we get the iconic "Hello, World ". If val is 127 we get "Hello, 127 ".

The most common use of format() is to output its result:

cout << format("Hello, {}
", val);

To see how this works, let’s first repeat the examples from (§11.6.1):

cout << format("{} {:x} {:o} {:d} {:b}
", 1234,1234,1234,1234,1234);

This gives the same output as the integer example in §11.6.1, except that I added b for binary which is not directly supported by ostream:

1234 4d2 2322 1234 10011010010

A formatting directive is preceded by a colon. The integer formatting alternatives are x for hexadecimal, o for octal, d for decimal, and b for binary.

By default, format() takes its arguments in order. However, we can specify an arbitrary order. For example:

cout << format("{3:} {1:x} {2:o} {0:b}
", 000, 111, 222, 333);

This prints 333 6f 336 0. The number before the colon is the number of the argument to be formatted. In the best C++ style, the numbering starts at zero. This allows us to format an argument more than once:

cout << format("{0:} {0:x} {0:o} {0:d} {0:b}
", 1234); // default, hexadecimal, octal, decimal, binary

The ability to place arguments into the output “out of order” is highly praised by people composing messages in different natural languages.

The floating-point formats are the same as for ostream: e for scientific, a for hexfloat, f for fixed, and g for default. For example:

cout << format("{0:}; {0:e}; {0:a}; {0:f}; {0:g}
",123.456);         // default, scientific, hexfloat, fixed, default

The result was identical to that from ostream except that the hexadecimal number was not preceded by 0x:

123.456; 1.234560e+002; 1.edd2f2p+6; 123.456000; 123.456

A dot precedes a precision specifier:

cout << format("precision(8): {:.8} {} {}
", 1234.56789, 1234.56789, 123456);
cout << format("precision(4): {:.4} {} {}
", 1234.56789, 1234.56789, 123456);
cout << format("{}
", 1234.56789);

Unlike for streams, specifiers are not “sticky” so we get:

precision(8): 1234.5679 1234.56789 123456
precision(4): 1235 1234.56789 123456
1234.56789

As with stream formatters, we can also specify the size of the field into which a number is to be placed and its alignment in that field.

Like stream formatters, format() can also handle time and dates (§16.2.2). For example:

cout << format("birthday: {}
",November/28/2021);
cout << format("zt: {}", zoned_time{current_zone(), system_clock::now()});

As usual, the default formatting of values is identical to that of default stream output formatting. However, format() offers a non-extensible mini-language of about 60 format specifiers allowing very detailed control over formatting of numbers and dates. For example:

auto ymd = 2021y/March/30 ;
cout << format("ymd: {3:%A},{1:} {2:%B},{0:}
", ymd.year(), ymd.month(), ymd.day(), weekday(ymd));

This produced:

ymd: Tuesday, March 30, 2021

All time and date format strings start with %.

The flexibility offered by the many format specifiers can be important, but it comes with many opportunities for mistakes. Some specifiers come with optional or locale dependent semantics. If a formatting error is caught at run time, a format_error exception is thrown. For example:

string ss = format("{:%F}", 2);           // error: mismatched argument; potentially caught at compile time
string sss = format("{%F}", 2);          // error: bad format; potentially caught at compile time

The examples so far have had constant formats that can be checked at compile time. The complimentary function vformat() takes a variable as a format to significantly increase flexibility and the opportunities for run-time errors:

string fmt = "{}";
cout << vformat(fmt, make_format_args(2));   // OK
fmt = "{:%F}";
cout << vformat(fmt, 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. For example:

string buf;
format_to(back_inserter(buf), "iterator: {} {}
", "Hi! ", 2022);
cout << buf;       // iterator: Hi! 2022

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

11.7 Streams

The standard library directly supports

  • Standard streams: streams attached to the system’s standard I/O streams (§11.7.1)

  • File streams: streams attached to files (§11.7.2)

  • String streams: streams attached to strings (§11.7.3)

  • Memory streams: stream attached to specific areas of memory (§11.7.4)

  • Synchronized streams: streams that can be used from multiple threads without data races (§11.7.5)

In addition, we can define our own streams, e.g., attached to communication channels.

Streams cannot be copied; always pass them by reference.

All the standard-library streams are templates with their character type as a parameter. The versions with the names I use here take chars. For example, ostream is basic_ostream<char>. For each such stream, the standard library also provides a version for wchar_ts. For example, wostream is basic_ostream<wchar_t>. The wide character streams can be used for unicode characters.

11.7.1 Standard Streams

The standard streams are

  • cout for “ordinary output”

  • cerr for unbuffered “error output”

  • clog for buffered “logging output”

  • cin for standard input.

11.7.2 File Streams

In <fstream>, the standard library provides streams to and from a file:

  • ifstreams for reading from a file

  • ofstreams for writing to a file

  • fstreams for reading from and writing to a file For example:

ofstream ofs {"target"};                   // “o” for “output”
if (!ofs)
         error("couldn't open 'target' for writing");

Testing that a file stream has been properly opened is usually done by checking its state.

ifstream ifs {"source"};                     // “i” for “input”
if (!ifs)
         error("couldn't open 'source' for reading");

Assuming that the tests succeeded, ofs can be used as an ordinary ostream (just like cout) and ifs can be used as an ordinary istream (just like cin).

File positioning and more detailed control of the way a file is opened is possible, but beyond the scope of this book.

For the composition of file names and file system manipulation, see §11.9.

11.7.3 String Streams

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

  • istringstreams for reading from a string

  • ostringstreams for writing to a string

  • stringstreams for reading from and writing to a string.

For example:

void test()
{
        ostringstream oss;

        oss << "{temperature," << scientific << 123.4567890 << "}";
        cout << oss.view() << '
';
}

The contents of an ostringstream can be read using str() (a string copy of the contents) or view() (a string_view of the contents). 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 (§11.3) by putting it into an istringstream.

A stringstream can be used for both reading and writing. For example, we can define an operation that can convert any type with a string representation into another that can also be represented as a string:

template<typename Target =string, typename Source =string>
Target to(Source arg)                // convert Source to Target
{
      stringstream buf;
      Target result;

      if (!(buf << arg)                                // write arg into stream
             || !(buf >> result)                      // read result from stream
             || !(buf >> std::ws).eof())         // is anything left in stream?
             throw runtime_error{"to<>() failed"};

      return result;
}

A function template argument needs to be explicitly mentioned only if it cannot be deduced or if there is no default (§8.2.4), so we can write:

auto x1 = to<string,double>(1.2);        // very explicit (and verbose)
auto x2 = to<string>(1.2);                    // Source is deduced to double
auto x3 = to<>(1.2);                              // Target is defaulted to string; Source is deduced to double
auto x4 = to(1.2);                                  // the <> is redundant;
                                                              // Target is defaulted to string; Source is deduced to double

If all function template arguments are defaulted, the <> can be left out.

I consider this a good example of the generality and ease of use that can be achieved by a combination of language features and standard-library facilities.

11.7.4 Memory Streams

From the earliest days of C++, there have been streams attached to sections of memory designated by the user, so that we can read/write directly from/to them. The oldest such streams, strstream, have been deprecated for decades, but their replacement, spanstream, ispanstream, and ospanstream, will not become official before C++23. However, they are already widely available; try your implementation or search GitHub.

An ospanstream behaves like an ostringstream11.7.3) and is initialized like it except that the ospanstream takes a span rather than a string as an argument. For example:

void user(int arg)
{
       array<char,128> buf;
       ospanstream ss(buf);
       ss << "write " << arg << " to memory
";
       // ...
}

Attempts overflow the target buffer sets the string state to failure11.4).

Similarly, an ispanstream is like an istringstream.

11.7.5 Synchronized Streams

In a multi-threaded system, I/O becomes an unreliable mess unless either

  • Only one thread uses the stream.

  • Access to a stream is synchronized so that only one thread at a time gains access.

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. For example:

void unsafe(int x, string& s)
{
       cout << x;
       cout << s;
}

A different thread may introduce a data race (§18.2) and lead to surprising output. An osyncstream can be used to avoid that

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

Other threads that also use osyncstreams will not interfere. Another thread that uses cout directly could interfere, so either use ostringstreams consistently or make sure that only a single thread produces output to a specific output stream.

Concurrency can be tricky, so be careful (Chapter 18). Avoid data sharing between threads whenever feasible.

11.8 C-style I/O

The C++ standard library also supports the C standard-library I/O, including printf() and scanf(). Many uses of this library are unsafe from a type and security point-of-view, so I don’t recommend its use. In particular, it can be difficult to use for safe and convenient input. It does not support user-defined types. If you don’t use C-style I/O but care about I/O performance, call

ios_base::sync_with_stdio(false);                  // avoid significant overhead

Without that call, the standard iostreams (e.g., cin and cout) can be significantly slowed down to be compatible with the C-style I/O.

If you like printf()-style formatted output, use format11.6.2); it’s type safe, easier to use, as flexible, and as fast.

11.9 File System

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

The filesystem library can handle unicode, but explaining how is beyond the scope of this book. I recommend the cppreference [Cppreference] and the Boost filesystem documentation [Boost] for detailed information.

11.9.1 Paths

Consider an example:

path f = "dir/hypothetical.cpp";      // naming a file

assert(exists(f));             // f must exist

if (is_regular_file(f))       // is f an ordinary file?
        cout << f << " is a file; its size is " << file_size(f) << '
';

Note that a program manipulating a file system is usually running on a computer together with other programs. Thus, the contents of a file system can change between two commands. For example, even though we first of all carefully asserted that f existed, that may no longer be true when on the next line, we ask if f is a regular file.

A path is quite a complicated class, capable of handling the varied character sets and conventions of many operating systems. In particular, it can handle file names from command lines as presented by main(); for example:

int main(int argc, char* argv[])
{
        if (argc < 2) {
               cerr << "arguments expected
";
               return 1;
        }

        path p {argv[1]};        // create a path from the command line

        cout << p << " " << exists(p) << '
';          // note: a path can be printed like a string
        // ...
}

A path is not checked for validity until it is used. Even then, its validity depends on the conventions of the system on which the program runs.

Naturally, a path can be used to open a file

void use(path p)
{
        ofstream f {p};
        if (!f) error("bad file name: ", p);
        f << "Hello, file!";
}

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

File System Types (partial)

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

Consider a simple, but not completely unrealistic, example:

void print_directory(path p)        // print the names of all files in p
try
{
      if (is_directory(p)) {
            cout << p << ":
";
            for (const directory_entry& x : directory_iterator{p})
                    cout << "   " << x.path() << '
';
      }
}
catch (const filesystem_error& ex) {
         cerr << ex.what() << '
';
}

A string can be implicitly converted to a path so we can exercise print_directory like this:

void use()
{
      print_directory(".");        // current directory
     print_directory("..");        // parent directory
     print_directory("/");         // Unix root directory
     print_directory("c:");       // Windows volume C

     for (string s; cin>>s; )
              print_directory(s);
}

Had I wanted to list subdirectories also, I would have said recursive_directory_iterator{p}. Had I wanted to print entries in lexicographical order, I would have copied the paths into a vector and sorted that before printing.

Class path offers many common and useful operations:

Path Operations (partial)

p and p2 are paths

value_type

Character type used by the native encoding of the filesystem: char on POSIX, wchar_t on Windows

string_type

std::basic_string<value_type>

const_iterator

A const BidirectionalIterator with a value_type of path

iterator

Alias for const_iterator

p=p2

Assign p2 to p

p/=p2

p and p2 concatenated using the file-name separator (by default /)

p+=p2

p and p2 concatenated (no separator)

s=p.native()

A reference to the native format of p

s=p.string()

p in the native format of p as a string

s=p.generic_string()

p in the generic format as a string

p2=p.filename()

The filename part of p

p2=p.stem()

The stem part of p

p2=p.extension()

The file extension part of p

i=p.begin()

The beginning iterator of p’s element sequence

i= p.end()

The end iterator of p’s element sequence

p==p2, p!=p2

Equality and inequality for p and p2

p<p2, p<=p2, p>p2, p>=p2

Lexicographical comparisons

is>>p, os<<p

Stream I/O to/from p

u8path(s)

A path from a UTF-8 encoded source s

For example:

void test(path p)

{
        if (is_directory(p)) {
               cout << p << ":
";
               for (const directory_entry& x : directory_iterator(p)) {
                       const path& f = x;    // refer to the path part of a directory entry
                       if (f.extension() == ".exe")
                               cout << f.stem() << " is a Windows executable
";
                      else {
                               string n = f.extension().string();
                               if (n == ".cpp" || n == ".C" || n == ".cxx")
                                      cout << f.stem() << " is a C++ source file
";
                      }
               }
        }
}

We use a path as a string (e.g., f.extension) and we can extract strings of various types from a path (e.g., f.extension().string()).

Naming conventions, natural languages, and string encodings are rich in complexity. The standard-library filesystem abstractions offer portability and great simplification.

11.9.2 Files and Directories

Naturally, a file system offers many operations and naturally, different operating systems offer different sets of operations. The standard library offers a few that can be implemented reasonably on a wide variety of systems.

File System Operations (partial)

p, p1, and p2 are paths; e is an error_code; b is a bool indicating success or failure

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 directories; report errors as error codes

b=copy_file(p1,p2)

Copy file contents from p1 to p2; report errors as exceptions

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

Many operations have overloads that take extra arguments, such as operating-system permissions. The handling of such is far beyond the scope of this book, so look them up if you need them.

Like copy(), all operations come in two versions:

  • The basic version as listed in the table, e.g., exists(p). The function will throw filesystem_error if the operation failed.

  • A version with an extra error_code argument, e.g., exists(p,e). Check e to see if the operations succeeded.

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.

Often, using an inquiry function is the simplest and most straightforward approach to examining the properties of a file. The <filesystem> library knows about a few common kinds of files and classifies the rest as “other”:

File types

f is a path or a file_status

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?

11.10 Advice

[1] iostreams are type-safe, type-sensitive, and extensible; §11.1.

[2] Use character-level input only when you have to; §11.3; [CG: SL.io.1].

[3] When reading, always consider ill-formed input; §11.3; [CG: SL.io.2].

[4] Avoid endl (if you don’t know what endl is, you haven’t missed anything); [CG: SL.io.50].

[5] Define << and >> for user-defined types with values that have meaningful textual representations; §11.1, §11.2, §11.3.

[6] Use cout for normal output and cerr for errors; §11.1.

[7] There are iostreams for ordinary characters and wide characters, and you can define an iostream for any kind of character; §11.1.

[8] Binary I/O is supported; §11.1.

[9] There are standard iostreams for standard I/O streams, files, and strings; §11.2, §11.3, §11.7.2, §11.7.3.

[10] Chain << operations for a terser notation; §11.2.

[11] Chain >> operations for a terser notation; §11.3.

[12] Input into strings does not overflow; §11.3.

[13] By default >> skips initial whitespace; §11.3.

[14] Use the stream state fail to handle potentially recoverable I/O errors; §11.4.

[15] We can define << and >> operators for our own types; §11.5.

[16] We don’t need to modify istream or ostream to add new << and >> operators; §11.5.

[17] Use manipulators or format() to control formatting; §11.6.1, §11.6.2.

[18] precision() specifications apply to all following floating-point output operations; §11.6.1.

[19] Floating-point format specifications (e.g., scientific) apply to all following floating-point output operations; §11.6.1.

[20] #include <ios> or <iostream> when using standard manipulators; §11.6.

[21] Stream formatting manipulators are “sticky” for use for many values in a stream; §11.6.1.

[22] #include <iomanip> when using standard manipulators taking arguments; §11.6.

[23] We can output time, dates, etc. in standard formats; §11.6.1, §11.6.2.

[24] Don’t try to copy a stream: streams are move only; §11.7.

[25] Remember to check that a file stream is attached to a file before using it; §11.7.2.

[26] Use stringstreams or memory streams for in-memory formatting; §11.7.3; §11.7.4.

[27] We can define conversions between any two types that both have string representation; §11.7.3.

[28] C-style I/O is not type-safe; §11.8.

[29] Unless you use printf-family functions call ios_base::sync_with_stdio(false); §11.8; [CG: SL.io.10].

[30] Prefer <filesystem> to direct use of platform-specific interfaces; §11.9.

..................Content has been hidden....................

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