CppStory Repo

Chapter #10 - C++ Stream Libraries

stream types and example code

10.0 Prologue

C++ provides a rich collection of standard libraries, with scores of well-designed facilities and more arriving with C++20. This chapter covers the standard I/O streams libraries. Fig 1. IOStream Hierarchy

10.1 Streams

The stream library structure appears in Fig 1. std::ios manages formatting and error information. Its derived classes - std::istream, std::ostream, and others - provide an invariant interface for streams, forming the language we use to interact with them. std::streambuf and its derived classes expose virtual functions that support customization. The interface is fixed so all code interacts with streams consistently, but semantics can be customized by implementing application-specific buffer adapters. I built such an example for graduate course CSE776 - Design Patterns, using the Adapter Pattern to route socket messages through a stream interface. Buffers provide the actual stream functionality. You can open, read, write, and close files using just an instance of std::filebuf.

10.2 Streams Formatting Code Examples

std::iostreams formatting is straightforward. Format settings are encoded as bits in a long int, accessed via these functions: long frmt = flags(); // retrieve the current format settings flags(frmt); // restore the original settings Most settings toggle individual bits when there are only two format choices. Three formats each have three choices:
  1. right, left, and internal adjust in a specified field width:
    std::cout.setf(ios::left, ios::adjustfield);
  2. scientific, fixed, and automatic for floating point numbers:
    std::cout.setf(ios::scientific, ios::floatfield);
  3. dec, oct, and hex for number formatting:
    std::cout.setf(ios::hex, ios::basefield);
Each choice occupies a single bit, so both left and right adjust could be set simultaneously, producing undefined behavior. The adjustfield, floatfield, and basefield arguments zero all bits for that format group, then set the one specified - ios::left, for example.
Formats can be set using flags(frmt) or setf(frmt, field), as shown below. They can also be set using iomanipulators - functions that modify part of the std::ios state by insertion into a stream. outstrm << setw(15); The insertion operator is overloaded to accept function pointers for this purpose.

Table 1. - Partial list of io manipulators

- full list
manipulator function
<ios> header included in <iostream> header
boolalpha, noboolalpha set text or numeric representations, respectively
showpoint, noshowpoint decimal point is always included in floating-point representation
skipws, noskipws leading whitespace is skipped on input
internal, left, right placement of fill characters
dec, oct, hex sets base used for integer I/O
fixed, scientific, hexfloat, defaultfloat sets formatting used for floating-point I/O
<iostream>
ends emits trailing '\0' to terminate string
flush flushes output stream
endl emits '\n' and flushes output stream
<iomanip>
resetiosflags clears specified ios_base flags
setiosflags sets specified ios_base flags
setfill changes fill character which is ' ' by default
setprecision specifies number of digits after the point (not precision of output)
setw changes width of the next input or output field
get-time parses date/time value of specified format
put-time formats and outputs a date/time value using specified format
quoted inserts and extracts quoted strings with embedded spaces
The code example below demonstrates most iostreams formatting mechanisms.
Example: std::iostream formats Formats.cpp /////////////////////////////////////////////////////////// // formats.cpp // // demonstrate ios formating // // // // Jim Fawcett, 24 Mar 96, modified 04 Mar 02 // /////////////////////////////////////////////////////////// #include <iostream> #include <iomanip> #include <string> using namespace std; void main() { title("Demonstrating ios formatting",'='); title("formating integers"); ///////////////////////////////////////////////////////////// // flags: // // skipws showbase showpoint uppercase showpos // // unitbuf stdio // ///////////////////////////////////////////////////////////// // basefield: // // dec hex oct // ///////////////////////////////////////////////////////////// long save = cout.flags(); // save format state cout.setf(ios::showbase|ios::uppercase); cout << " " << 59 << " in hexadecimal is " << hex << 59 << dec << endl << " " << 59 << " in octal is " << oct << 59 << dec << endl; cout.flags(save); // restore original format state title("formating doubles"); ///////////////////////////////////////////////////////////////// // function: // // width() width(n) fill() fill(n) // // precision() precision(n) // ///////////////////////////////////////////////////////////////// // floatfield: // // scientific fixed automatic // ///////////////////////////////////////////////////////////////// cout.precision(6); cout.setf(ios::right, ios::adjustfield); cout.setf(ios::scientific, ios::floatfield); cout.setf(ios::showpoint); double d = 9; int i; for(i=0; i<5; i++) cout << setw(14) << (d /= 3); cout << endl; cout.flags(save); title("adjust, fill, and width"); ///////////////////////////////////////////////////////////////// // adjustfield: // // left right internal // ///////////////////////////////////////////////////////////////// char *numbers[5] = { "zero", "one", "two", "three", "four" }; int nums[5] = { 0, 1, 2, 3, 4 }; cout.fill('.'); for(i=0; i<5; i++) { cout.setf(ios::left, ios::adjustfield); cout << " " << setw(30) << numbers[i]; cout.setf(ios::right, ios::adjustfield); cout << setw(30) << nums[i] << endl; } cout.flags(save); title("stream manipulators"); ///////////////////////////////////////////////////////////// // manipulators: // // iostream.h - dec, oct, hex, endl, ws, ends, flush, // // iomanip.h - // // setfill(char), setw(int), setprecision(int), // // setiosflags(long), resetiosflags(long) // ///////////////////////////////////////////////////////////// cout << " " << setprecision(5) << (d = 1.0/3.0) << endl; cout << " " << setfill('.') << setw(20) << d << endl; cout << " " << setiosflags(ios::left | ios::showpos) << setw(20) << d << endl; cout << " " << resetiosflags(ios::left | ios::showpos) << d << endl; cout.flags(save); cout << endl; } Output: ============================== Demonstrating ios formatting ============================== formating integers -------------------- 59 in hexadecimal is 0X3B 59 in octal is 073 formating doubles ------------------- 3.000000e+00 1.000000e+00 3.333333e-01 1.111111e-01 3.703704e-02 adjust, fill, and width ------------------------- zero.......................................................0 one........................................................1 two........................................................2 three......................................................3 four.......................................................4 stream manipulators --------------------- 0.33333 .............0.33333 +0.33333............ 0.33333

10.3 File Streams Code Examples

The next example demonstrates file stream operations, covering most tasks you encounter when working with files.
Files open in two ways and close as follows:
  1. std::ifstream in(filename, ios::in);
  2. std::ifstream in; in.open(filename, ios::in);
  3. in.close();
File stream destructors call close() when the stream goes out of scope, but closing early is harmless - it frees the internal OS file handle immediately.
The example below illustrates error handling for file open operations that may fail.
  1. The file name may be incorrect or the file may not exist.
  2. Other code may have that file opened.
  3. The user may not have permissions to open the file.
Standard streams support a simple error handling model. A stream exists in one of three states:
  1. good state, tested with the function bool good(). When good is true the stream operates normally.
  2. bad state, tested with bool bad(). Streams in a bad state are fully functional, but will not respond to any commands until the stream error is cleared with void clear(). A state can go bad when it attempts to read an end of file integer, eof. To restore normal operation, clear the error and back up at least one character by calling strm.seek(-1);
  3. fail state, tested with bool fail(). Streams in a fail state have been corrupted. The only thing you can do with a failed stream is to destroy it or re-initialize with an open(filename, ios attributes);.
Always check file stream state after opening, before any use. The common pattern: std::ofstream out(filename, ios::out);
if(!out.good()) { /* do some clean up and return */ }
The example follows:
Example: std::fstreams I/O Fileio.cpp /////////////////////////////////////////////////////////////// // fileio.cpp - demonstrate fstreams // // // // Jim Fawcett, 24 Mar 96, modified 04 Mar 02 // /////////////////////////////////////////////////////////////// #include <iostream> // cout, << #include <fstream> // ifstream(), <<, >> #include <cstdlib> // exit(1); #include <sstream> #include <string> using namespace std; //----< display titles >--------------------------------------- void title(const char *t, char ul = '-', ostream &out = cout) { int len = strlen(t) + 2; string line(len,ul); if(ul == '=') out << "\n " << line; out << "\n " << t << "\n " << line << endl; } //----< begin demonstration >---------------------------------- void main(int argc, char *argv[]) { ///////////////////////////////////////////////// title("Demonstrating Basic File Operations",'='); ///////////////////////////////////////////////// if(argc < 2) { cout << "\nplease enter name of text file on command line\n\n"; exit(1); } const int bufSize = 80; char buffer[bufSize]; // create a scope with { } and define an input stream inside // then read line-by-line and send to standard output { ifstream in(argv[argc-1]); if(!in.good()) { cout << "can't open file " << argv[argc-1] << endl; exit(1); } else { ostringstream temp; temp << "processing file " << argv[argc-1] << " using istream::getline()"; title(temp.str().c_str()); ////////////////////////// } while(in.good()) { in.getline(buffer,bufSize); cout << buffer << endl; } } // in goes out of scope and is destroyed // define stream again at global level, get pointer to // its filebuf and stream input directly to output ostringstream temp; temp << "processing file " << argv[argc-1] << " using filebuf::rdbuf()"; title(temp.str().c_str()); ////////////////////////// ifstream in(argv[argc-1]); filebuf *bptr = in.rdbuf(); // get pointer to stream cout << bptr << endl; // stream input to output in.close(); // still in scope so close it // open stream again, seek to end to find size, and // backup half way, then send last half to stdout in.open(argv[argc-1]); // open it again in.seekg(0, ios::end); // go to end of file streampos sp = in.tellg(); // size = number of bytes from beg in.seekg(-sp/2, ios::end); // go to middle in.getline(buffer,bufSize); // go to next newline ostringstream temp2; temp2 << "processing last half of this " << sp << " byte file"; title(temp2.str().c_str()); /////////////////////////// cout << in.rdbuf() << endl; // output from that point to end title("stream state goes bad when attempting to read past end of file"); //////////////////////////////////////////////////////////////////////// char ch; in >> ch; if(!in.good()) cout << " attempting to read past EOF makes stream state" << " not good\n"; title("can't read any more until we clear stream to good state"); ///////////////////////////////////////////////////////////////// in.clear(); if(in.good()) cout << " stream state good after clear()\n"; in.seekg(-1,ios::end); // back up to good char in >> ch; if(in.good()) cout << " after backing up and reading " << ch << " stream state still good\n"; in >> ch; if(!in.good()) cout << " after reading EOF stream state not good again\n"; cout << endl; } Output: ===================================== Demonstrating Basic File Operations ===================================== processing file test.txt using istream::getline() --------------------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line processing file test.txt using filebuf::rdbuf() ------------------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line processing last half of this 106 byte file -------------------------------------------- fifth line this is the sixth and final line stream state goes bad when attempting to read past end of file ---------------------------------------------------------------- attempting to read past EOF makes stream state not good can't read any more until we clear stream to good state --------------------------------------------------------- stream state good after clear() after backing up and reading e stream state still good after reading EOF stream state not good again

10.4 Filebuf I/O Code Examples

The next example shows that most stream object operations are also available directly on its internal filebuf. The stream object presents a non-virtual interface; its filebuf does the actual work.
Example: std::Filebuf I/O Filebuf.cpp ///////////////////////////////////////////////////////////////// // filebuf.cpp // // demonstrate low level input and output using streambufs // // built from FILE pointer and stdout // // // // Jim Fawcett, 24 Mar 96, modified 23 Mar 97, 01 Mar 04 // ///////////////////////////////////////////////////////////////// #include <iostream> // cout, << #include <fstream> // ifstream(), <<, >> using namespace std; //----< display titles >--------------------------------------- void title(const char *t, char ul = '-', ostream &out = cout) { int len = strlen(t) + 2; std::string line(len,ul); if(ul == '=') out << "\n " << line.c_str(); out << "\n " << t << "\n " << line.c_str() << endl; } //----< begin demonstration >---------------------------------- void main(int argc, char *argv[]) { ////////////////////////////////////////////////////////////// title("Demonstrating use of Stream Buffers with File IO",'='); ////////////////////////////////////////////////////////////// if(argc < 2) { cout << "\nplease enter name of text file on command line\n"; exit(1); } title("using streambuf for input from file"); ///////////////////////////////////////////// filebuf ifb; // create filebuf ifb.open(argv[argc-1],ios::in); // attach to file cout << &ifb << endl; // stream to cout cout.flush(); title("using streambuf for output to stdout"); ////////////////////////////////////////////// istream in(&ifb); // make stream in, attached to ifb in.seekg(0); // move to top of input buffer ostream out(cout.rdbuf()); // make stream out attached to cout streambuf out << &ifb << endl; // stream it to out out.flush(); // could replace last statement with the lower level // gets and puts shown below title("using low-level streambuf interface for output to stdout"); ////////////////////////////////////////////////////////////////// streambuf* pOfb = cout.rdbuf(); in.seekg(0); char ch; while((ch = ifb.sbumpc()) != EOF) pOfb->sputc(ch); cout << "\n\n"; } Output: ================================================== Demonstrating use of Stream Buffers with File IO ================================================== using streambuf for input from file ------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line using streambuf for output to stdout -------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line using low-level streambuf interface for output to stdout ---------------------------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line

10.5 Read-Write Code Examples

The next example demonstrates reading and writing to the same file through a single filebuf. This approach suits a database record manager that reads existing data and writes new data to the same file. Making this useful requires a schema defining file regions for reading and writing specific record columns - the approach SQL databases take. This example demonstrates concurrent reading and writing, without schema-based record management. The approach wraps a shared filebuf with both an input stream and an output stream. Details:
Read/Write Streams Readwrite.cpp /////////////////////////////////////////////////////////////// // readwrit.cpp - demonstrate read/write fstreams // // with buffer location seeking // // Jim Fawcett, 24 Mar 96, modified 23 Mar 97 // /////////////////////////////////////////////////////////////// #include <iostream> // cout, << #include <fstream> // ifstream(), <<, >> #include <cstdlib> // exit(1); #include <string> using namespace std; //----< display titles >--------------------------------------- void title(const char *t, char ul = '-', ostream &out = cout) { int len = strlen(t) + 2; string line(len,ul); if(ul == '=') out << "\n " << line; out << "\n " << t << "\n " << line << endl; } //----< begin demonstration >---------------------------------- void main(int argc, char *argv[]) { /////////////////////////////////////////////////////////////// title("Demonstration of Reading AND Writing to a Common File",'='); /////////////////////////////////////////////////////////////// if(argc < 2) { cout << "please enter file name on command line\n"; exit(1); } /////////////////////////////////////////////////////////////// // file open modes: in out app ate // // nocreate noreplace trunc // /////////////////////////////////////////////////////////////// // save input file contents in a temporary file // for reading and writing ifstream masterin(argv[argc-1]); ofstream tempout("tmp.tmp"); tempout << masterin.rdbuf(); masterin.close(); tempout.close(); // open temporary for input AND output processing ifstream in("tmp.tmp", ios::in|ios::out); if(!in.good()) { cout << "can't open file tmp.tmp" << endl; exit(1); } // use input stream buffer for output stream too ostream out(in.rdbuf()); // now, try reading and writing title("this is a test file for reading and writing"); ///////////////////////////////////////////////////// cout << in.rdbuf(); out << "---\nthis text is added to the end\n"; // how many bytes in file? streampos sp = out.tellp(); out.seekp(-sp, ios::end); // back up from end out << "this text overwrites beginning of file\n"; cout << endl; // write it out to see what happened title("modified file:"); //////////////////////// in.seekg(ios::beg); // go to beginning cout << in.rdbuf() << endl; } Output: ======================================================= Demonstration of Reading AND Writing to a Common File ======================================================= this is a test file for reading and writing --------------------------------------------- this is the first line second line third line fourth line fifth line this is the sixth and final line modified file: ---------------- this text overwrites beginning of file rd line fourth line fifth line this is the sixth and final line--- this text is added to the end

10.6 String Streams Code Examples

The final example demonstrates stringstreams. They are especially useful for object-to-string and string-to-object transformations - what std::ostream does. Writing: double d{ 3.14159 };
std::cout << d;
std::cout transforms its double argument into a character sequence written to your terminal - a string. All standard streams do this. stringstream objects let you retrieve that string for processing instead of sending it to a terminal. The demo:
Example: std::stringstreams Strio.cpp /////////////////////////////////////////////////////////////// // strio.cpp - demonstrate stringstreams // // // // Jim Fawcett, 24 Mar 96, modified 04 Mar 02 // /////////////////////////////////////////////////////////////// #include <iostream> // cout, << #include <sstream> // istringstream(), ostringstream(), <<, >> #include <string> using namespace std; //----< display titles >--------------------------------------- void title(const char *t, char ul = '-', ostream &out = cout) { int len = strlen(t) + 2; string line(len,ul); if(ul == '=') out << "\n " << line; out << "\n " << t << "\n " << line << endl; } //----< begin demonstration >---------------------------------- void main() { /////////////////////////////////////////////////// title("Demonstrating stringstream operations",'='); /////////////////////////////////////////////////// title("reading from istringstream"); ///////////////////////////// istringstream source("15 3.1415927 Now is the hour"); cout << "\n " << source.str() << endl; title("writing to ostringstream"); //////////////////////////////////// ostringstream destin; destin << source.str(); stringbuf *pStringBuf = destin.rdbuf(); cout << "\n--source-----" << source.str(); cout << "\n--destin-----" << destin.str(); cout << "\n--stringBuf--" << pStringBuf->str() << endl; title("writing to custom buffer no longer supported in ostringstream"); /////////////////////////////////////////////////////////////////////// title("parsing input buffer"); ////////////////////////////// int i; const int bufSize=50; double d; char savebuf[bufSize]; source.seekg(0); source >> i >> d; cout << endl; cout << " i = " << i << endl; cout << " d = " << d << endl; int j; // The following operation is dangerous! // extraction writes whole word into buffer, // even if word size is larger than buffer for(j=0; j<4; j++) { source >> savebuf; cout << " savebuf = " << savebuf << endl; } // Here is a safer, though less convenient way // to read strings from a stream title("safe parsing using destination string"); /////////////////////////////////////////////// source.clear(); source.seekg(0); string temp; while(source.good()) { source >> temp; cout << "\n " << temp; } cout << "\n\n"; title("char-by-char parsing"); ////////////////////////////// source.clear(); source.seekg(0); temp = ""; while(source.good()) { char ch = source.get(); if(!isspace(ch)) temp += ch; else if(temp.size() > 0) { cout << "\n " << temp.c_str(); temp = ""; } } if(temp.size() > 0) cout << "\n " << temp.c_str(); cout << "\n\n"; } Output ======================================= Demonstrating stringstream operations ======================================= reading from istringstream ---------------------------- 15 3.1415927 Now is the hour writing to ostringstream -------------------------- --source-----15 3.1415927 Now is the hour --destin-----15 3.1415927 Now is the hour --stringBuf--15 3.1415927 Now is the hour parsing input buffer ---------------------- i = 15 d = 3.14159 savebuf = Now savebuf = is savebuf = the savebuf = hour safe parsing using destination string --------------------------------------- 15 3.1415927 Now is the hour char-by-char parsing ---------------------- 15 3.1415927 Now is the hour�

10.7 Epilogue

Commentary to be added later

10.8 References

string formatting (see answer 15) - stackoverflow