A Gentle Introduction to IO Streams in C++ (2024)

Starting out

How to begin
Get the book


Tutorials

C tutorial
C++ tutorial
Game programming
Graphics programming
Algorithms
More tutorials


Practice

Practice problems
Quizzes


Resources

Source code
C and C++ tips
Getting a compiler
Book recommendations
Forum


References

Function reference
Syntax reference
Programming FAQ

By Manasij Mukherjee

One of the great strengths of C++ is its I/O system, IO Streams. As Bjarne Stroustrup says in his book "The C++ Programming Language", "Designing and implementing a general input/output facility for a programming language is notoriously difficult". He did an excellent job, and the C++ IOstreams library is part of the reason for C++'s success. IO streams provide an incredibly flexible yet simple way to design the input/output routines of any application.

IOstreams can be used for a wide variety of data manipulations thanks to the following features:

  • A 'stream' is internally nothing but a series of characters. The characters may be either normal characters (char) or wide characters (wchar_t). Streams provide you with a universal character-based interface to any type of storage medium (for example, a file), without requiring you to know the details of how to write to the storage medium. Any object that can be written to one type of stream, can be written to all types of streams. In other words, as long as an object has a stream representation, any storage medium can accept objects with that stream representation.
  • Streams work with built-in data types, and you can make user-defined types work with streams by overloading the insertion operator (<<) to put objects into streams, and the extraction operator (>>) to read objects from streams.
  • The stream library's unified approach makes it very friendly to use. Using a consistent interface for outputting to the screen and sending files over a network makes life easier. The programs below will show you what is possible.

The IO stream class hierarchy is quite complicated, so rather than introduce you to the full hierarchy at this point, I'll start with explaining the concepts of the design and show you examples of streams in action. Once you are familiar with elements of the design and how to apply those concepts to design a robust I/O system for your software, an understanding of what belongs where in the hierarchy will come naturally.

What do input and output really mean?

To get a good idea about what input and output are, think of information as a stream of characters. This makes sense because whatever we enter through a keyboard can only be characters. Suppose the user enters the number 7479....WAIT...! How do you know the user entered a number? The problem is that you don't really know. All you have is a set of 4 characters: '7', '4', '7' and '9'. It is completely up to you, the programmer, whether you want the input to be a number, to be a string, or to be fodder for /dev/random; whether the characters can be valid for the desired type totally depends upon whether that type can interpret the characters in the input stream as a description for an object of that type.

You have to get the input characters into a recognizable data type for them to be of any use other than as a character array.

IO streams not only define the relation between a stream of characters andthe standard data types but also allows you to define a relationship between astream of characters and your own classes. It also allows you nearly limitlessfreedom to manipulate those streams both using object oriented interfaces andworking directly on character buffers when necessary. (Of course some of the lower level manipulations may be undefined; for example, you can't probe forward into an input stream to see the future!)

How do streams work?

Streams are serial interfaces to storage, buffers files, or any otherstorage medium. The difference between storage media is intentionally hidden by the interface; youmay not even know what kind of storage you're working with but the interface isexactly the same.

The "serial" nature of streams is a very important element of their interface.You cannot directly make random access random reads or writes in a stream(unlike, say, using an array index to access any value you want) although youcan seek to a position in a stream and perform a read at that point.

Using a serial representation gives a consistent interface for all devices.Many devices have the capability of both producing and consuming data at thesame time; if data is being continually produced, the simplest way to thinkabout reading that data is by doing a fetch of the next characters in a stream.If that data hasn't been produced yet (the user hasn't typed anything, or thenetwork is still busy processing a packet), you wait for more data to becomeavailable, and the read will return that data. Even if you try to seek past theend (or beginning) of a stream, the stream pointer (i.e. get or put pointer)will remain at the boundary, making the situation safe. (Compare this withaccessing data off the end of an array, where the behavior is undefined.)

The underlying low-level interface that corresponds to the actual mediumvery closely is a character buffer (the stream buffer, technically called thestreambuf), which can be thought of as the backbone of the stream. Being abuffer, it does not hold the entire content of the stream, if the stream islarge enough, so you can't use it for random access.

The most important of the basic stream operations are:

  1. First, the stream is initialized with the appropriate type (like a std::string for a stringstream and the filename for an fstream) of values and suitable modes (like ios::in for input and ios::out for output and many more depending on the type of the stream).
  2. After that, you can specify where the I/O should occur, through the get and put pointers. Depending on how you open the stream, the location may already be set appropriately (for example, if you open a file with ios::app, your get pointer set at the end of the stream, allowing appends).

    The member functions associated with setting the get and put pointers are:

    • seekg()and seekp() for .dragging. the get and put pointer, respectively, to the position specified.Both seek methods take an argument (of type streampos) providing a position in the file relative to the beginning of the file (using ios::beg), the end of the file (using ios::end), or the current position (using ios::cur). You may also provide just a specific location, such as io::beg, for the beginning of the file.
    • tellg() and tellp() provide the current location of the get and put pointers, respectively

    The following one-liners should clear up most questions:

    seekg(0); seekg(0,ios::beg); //sets the get pointer to the beginning.seekg(5,ios::beg); //sets the get pointer to 5 chars forward of the beginning.tellp(); tellg() //returns the current value of the put/get pointerseekp(-10,ios::end); //sets the put pointer to 10 chars before the endseekp(1,ios::cur); //proceeds to next char

    N.B: Be careful when seeking the put pointer into the middle of in thestream. If you put anything in the stream, it will go directly into the stream atthe put location, overwriting the previous contents. In other words, if youneed to insert data in the middle of a stream, you have to manually move thedata that would be overwritten. As a side note, if you're finding yourselfdoing that too often, then you may want to use a string representation of yourdata, which can simplify this kind of random access operation.

  3. Once you're at the right location in the stream, input and output is done through the << and >> operators. If you want to input an object to the stream, use the << operator; for output, use >>. The class for your object must, of course, have provided overloads for these methods. Here's a short example:
    //Inserts var into string (like objects are displayed by // putting them to cout)output_stream<<var; //Gets the value from the stream's characters positioned // after the get pointer and puts it into var.input_stream>>var; 

    If var is an object (either a built in class or a user defined type), theexact process of the input or output is dependent on the overloaded >> or<< operator respectively.

Error handling with IO streams

Handling errors gracefully is important for building a robust system. The'errors' or 'signals' (e.g., reaching the end of the file) in this casegenerally occur when a stream encounters something it didn't expect.

Whether a stream is currently valid can be checked by simply by using the stream as a Boolean:

ifstream file( "test.txt" );if ( ! file ){ cout << "An error occurred opening the file" << endl;}

More detailed status of the stream can be obtained using the good(), bad(), fail() and eof() functions. The clear() function will reset the stream and clear the error, which is necessary to perform any further IO on the stream.

  • good() returns true when everything is okay.
  • bad() returns true when a fatal error has occurred.
  • fail() returns true after an unsuccessful stream operation like an unexpected type of input being encountered.
  • eof() returns true when the end of file is reached.

You can detect that a particular read or write operation failed by testing the result of the read. For example, to check that a valid integer is read from the user, you can do this:

int x;if ( cin >> x ) { cout << "Please enter a valid number" << endl;}

This works because the read operation returns a reference to the stream.

An example of creating a stream-enabled object

Here is a simple example of an utility designed for writing out logfile entries from command line arguments that takes advantage of some important stream facilities. If you don't understand something, refer to the tutorial on that particular topic.

#include <iostream>#include <ctime>#include <sstream>#include <fstream>using namespace std;// timestamp returns the current time as a string std::string timestamp(); class LogStatement;ostream& operator<<(ostream& ost, const LogStatement& ls);class LogStatement{public: LogStatement(std::string s): data(s), time_string( timestamp() ) { }; //This method handles all the outputs. friend ostream& operator<<(ostream&, const LogStatement&); private: std::string data; std::string time_string; };ostream& operator<<(ostream& ost, const LogStatement& ls){ ost<<"~|"<<ls.time_string<<'|'<<ls.data<<"|~"; return ost;}std::string timestamp() { //Notice the use of a stringstream, yet another useful stream medium! ostringstream stream; time_t rawtime; tm * timeinfo; time(&rawtime); timeinfo = localtime( &rawtime ); stream << (timeinfo->tm_year)+1900<<" "<<timeinfo->tm_mon <<" "<<timeinfo->tm_mday<<" "<<timeinfo->tm_hour <<" "<<timeinfo->tm_min<<" "<<timeinfo->tm_sec; // The str() function of output stringstreams return a std::string. return stream.str(); }int main(int argc, char** argv){ if(argc<2) { // A return of -1 denotes an error condition. return -1; } ostringstream log_data; // This takes all the char arrays in the argv // (except the filename) and produces a stream. for(int i=1;i<argc;i++) { log_data<<argv[i]<<' '; } LogStatement log_entry(log_data.str()); clog<<log_entry<<endl; ofstream logfile("logfile",ios::app); // check for errors opening the file if ( ! logfile ) { return -1; } logfile<<log_entry<<endl; logfile.close(); return 0;}

This example should be pretty straightforward. The only two 'new' things here are:

  1. The type ostream, which is the base class for any stream that accepts writes, such as ofstream and ostringstream. The standard stream objects: std::cout, std::cerr, std::clog and their 'wide' versions (e.g. std::wcout), are objects of this ostream class.
  2. The use of stringstreams to simplify string handling. See the section on stringstreams below for more details on how stringstreams work.

A few other things to note

The << operator takes in an ostream object, modifies it andreturns it, even though it would have been enough to take the ostream objectand modify it, with no return value. The value of returning the object is thatyou can chain operations, as in the following statement:

cout<<"Hey, I'm "<< n <<" years old.";

As the operator accepts an ostream, you can do anything with the datawritten to the stream simply by choosing a different subclass of ostream. Theexample shows writing to a file and to the system log console/file. You couldalso compress it and archive it, send it over a network, parse it later withanother program to determine the number of log entries in a specified timeinterval, etc. Basically, you can use the class for any sort of outputoperation for which a subclass of ostream exists.

Parts of the IO stream library

Now that you've seen the basic problems solved by IO streams and how they work, let's look at the different elements of the IO streams library, with a few examples of each in action.

Standard Stream Objects for Console I/O: (cout, cin, cerr, clog, etc.)

These are declared in the <iostream> header and provide a consistent interface for console I/O. They allow you a lot of control about the exact way you want to read a value from the stream into the given variable (or write the variable to the stream).

File I/O

File I/O is done by manually declaring objects of the ifstream, ofstream orfstream classes (from the <fstream> header) and then associating a filewith the stream by using the stream's open method with the file's name as anargument. File I/O is particularly important because files are often used torepresent a large variety of media, such as the console, devices, disk files,virtual memory, list of running processes and even the black hole '/dev/null'.This is especially true on *nix systems where it is commonly said that"Everything is a file".

Here is a very very simple example of writing to a file:

#include <iostream>#include <fstream>using namespace std;int main(){ ofstream ofs("a.txt",ios::app); if(ofs.good()) { ofs<<"Hello a.txt, I'm appending this on you."; } return 0;}

String Streams

Strings are streams and streams are strings. Both are character arrays, buteach has a totally different interface (random access strings vs serialstringstreams). By providing both std::string and stringstreams, the C++standard library ensures that you have the flexibility to choose eitherinterface for your design.

By including the <sstream> header, you can make objects of theistringstream, ostringstream and stringstream types. These objects make certainkinds of string manipulations much easier.

You can, for example, open a string in a stringstream, extract a floating point number from it to do some operations, and put it back in the stream.

#include <iostream>#include <sstream>using namespace std;int main(){ stringstream my_stream(ios::in|ios::out); std::string dat("Hey, I have a double : 74.79 ."); my_stream.str(dat); my_stream.seekg(-7,ios::end); double val; my_stream>>val; val= val*val; my_stream.seekp(-7,ios::end); my_stream<<val; std::string new_val = my_stream.str(); cout<<new_val; return 0;}

The output is

Hey, I have a double : 5593.54

The lower level, where streams meet buffers

This is the most interesting and confusing part of this library, letting youmanipulate streams at their lowest levels, bytes and bits. This is accomplishedby the abstract class streambuf from which stringbuf and filebuf are derived.Every stream object has one of those as their backbones. Their function rdbuf()lets you access a pointer to the underlying stream buffer.

Here is a very simple example of copying a file efficiently with those buffers (thankfully, no ultra-complicated manipulation is involved here!).

#include <iostream>#include <fstream>using namespace std;int main(){ ifstream ifs("a.txt"); //ios::trunc means that the output file will be overwritten if exists ofstream ofs("a.txt.copy", ios::trunc); if (ifs && ofs ) { ofs<<ifs.rdbuf(); } return 0;}

Popular pages

  • Jumping into C++, the Cprogramming.com ebook
  • How to learn C++ or C
  • C Tutorial
  • C++ Tutorial
  • 5 ways you can learn to program faster
  • The 5 most common problems new programmers face
  • How to set up a compiler
  • How to make a game in 48 hours

Advertising | Privacy policy |Copyright © 2019 Cprogramming.com | Contact | About



A Gentle Introduction to IO Streams in C++ (2024)

References

Top Articles
Latest Posts
Article information

Author: Delena Feil

Last Updated:

Views: 5395

Rating: 4.4 / 5 (45 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Delena Feil

Birthday: 1998-08-29

Address: 747 Lubowitz Run, Sidmouth, HI 90646-5543

Phone: +99513241752844

Job: Design Supervisor

Hobby: Digital arts, Lacemaking, Air sports, Running, Scouting, Shooting, Puzzles

Introduction: My name is Delena Feil, I am a clean, splendid, calm, fancy, jolly, bright, faithful person who loves writing and wants to share my knowledge and understanding with you.