Part II c++ library
IO (Chapter 8)
Headers
IO Classes are located in following headers: iostream, fstream, sstream.
Wide characters
To support languages that contain wide characters, the `wchar_t` type exists.
Wide-character versions of functions are prefixed with a `w` such as wcin,wout and so forth.
Wide character-types & functions are contained in the same headers as the plain char types.
Inheritance model
The IO classes make use of an inheritance hierarchy. As such, the way we interact with the classes does not change irrespective of whether we are reading (writing) to a file or console, nor network.
ifstream and istringstream both inherit from istream. We can therefore use >> to read from a stream & getline without knowing the stream type. Similarly we can write using >> and writeline.
No Copy or assign
Objects of the IO types cannot be copied nor assigned
ofstream o1,o2; o1 = o2; // cannot assign stream objects
ofstream print(ofstream); // cannot initialise
o2 = print(o2); // cannot copy stream objects
As IO types are not copyable, we typically pass and return IO types as references. As both reading and writing an IO object change its state these references cannot be const.
Condition States
As IO operations can fail due to circumstances outside of the program scope: disk failures, bad input etc, the IO classes define functions and flags which let us access the condition state of a stream.
int a;
std::cin >> a; // if a non-int is entered the read will fail.
If a user enters 'a' as the input, cin will enter an error state. The easiest way to determine state of a stream is use it as a condition:
while(cin>>a) {
// read operation successful
}
Interrogating Stream state
Using a stream as a condition informs us only whether a stream is valid, not what occurred to invalidate it. Cause of invalidation can be important as reaching EOF vs out of disk warrant different responses.
The IO library offers a iostate integral type allowing the user to extract context on the reason for a failure. This type is implemented in a machine-dependent manner. The implementation defines four constexpr values which represent particular bit patterns:
eofbitbadbidfailbitgoodbit
As the goodbit is guaranteed to have the value of 0 it can be used in conditionals. The library offers a set of functions to interrogate the state of the flags:
s.eof()s.fail()s.bad()s.good()s.clear()s.setstate()s.rdstate()
The best way to determine stream state is using s.good() and s.fail(). An if a stream has a failed state, its state can be cleared and the stream reused.
cin.clear()
cin >> a
We can use bit operations in the clear function to set state.
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit)
Managing the Output Buffer
Each output buffer manages a buffer which is uses to hold data which is being read and written. This means calls to << may result in immediate output, or the OS may store it in a buffer and the content printed later. Buffers allow the OS to combine several output operations from our application into a single system-level write.
As writing to a device can be time-consuming, allowing the OS to buffer can provide a significant performance benefit.
Flushing the Output Buffer
There are several conditions that cause the buffer to be flushed
- Program exits normally
- Buffer is full
endl- Buffer's
unitbufmanipulator is set to empty() buffer on every output operation. Apply withcout << unitbuf - If output stream tied to another stream it is flushed whenever stream to which it is tied is read/written
NB: By default, cin and cerr are both tied to cout, so reading to cin or writing to cerr in turn flushes buffer in cout.
Caution: Buffers are not flushed if the program crashes. This is key when investigating cause of a crash. Often the cause itself is not output because any recent writes are sitting in the buffer leading to a red herring!
File IO
The types provided for file manipulation ifstream, ofstream and fstream offer the same operators (<< and >>) to read/write to files. In addition to the behaviour inherited from iostream, the types offer operations specific to files:`
f.open()f.is_open()f.close()
To read/write to a file, associate a filestream object to that file
ifstream in(filename)
ofstream out(filename)
As the ifstream, ofstream & fstreamtypes inherit from iostream, they can be provided in-place for any function which takes iostream.
open/close members
If we construct an ifstream with a filename it is automatically opened for us. Alternatively, we can create an ofstream which is not associated with any file, but will open a file on the .open() call.
ifstream in(file); // file opened for read
ofstream out; // no file association
out.open(file); // file opened for write
Similar to the conditional use with cin, we should use if(out) and if(in)prior to calling read and write.
NB: We can make use of RAII when using file stream objects, as the streams are automatically closed on destruction. Be weary, however, if re-using a fstream object before it goes out of scope the file will need to be manually closed.
File modes
Each stream has a file mode associated with it. The below table outlines the available modes:
- in
- out
- app (append, all writes are appended, no manual file seeking)
- ate (seek to end after open, but free to seek thereafter)
- trunc (truncate)
- binary (all io performed in binary)
The file mode is determined each time open is called.
string Streams
The sstream header allows manipulation of a whole line of text using the same types and operators asiostream. The shared interface between these libraries is their only link, and no open nor close operation is supported on a streamstream.
The istringstream is often used when we want to work on a whole line, or the words within a line as we can take advantage of delimiting by space and similar.
string line, word;
vector<Book> books;
while(getline(cin,line)) {
Book book;
istringstream record(line);
record >> book.title;
while(record >> word) {
book.contents.push_bash(word); // store metadata
}
}