
Design: The IOStream library is designed with a rigid separation of responsibilities.
(a) The classes derived from basic_ios handle only formatting of the data (actually it doesn't even do formatting. The formatting is delegated to corresponding facets in the locale library).
(b) The reading and writing of characters is performed by the stream buffers maintained by the basic_ios subobjects. The stream buffers supply character buffers for reading and writing. In addition, an abstraction from the external representation, such as files or strings, is formed by the stream buffers.
Thus, stream buffers play an important role when performing I/O with new external representations (such as sockets or graphical user interface components), redirecting streams, or combining streams to form pipelines (for example, to compress output before writing to another stream).
Also, the stream buffer synchronizes the I/O when doing simultaneous I/O on the same external representation. By using stream buffers, it is quite easy to define access to a new “external representation,” such as a new storage device. All that has to be done is to derive a new stream buffer class from basic_streambuf<> or an appropriate specialization and to define functions for reading and/or writing characters for this new external representation. All options for formatted I/O are available automatically if a stream object is initialized to use an object of the new stream buffer class.
(c) sentry auxiliary class for preprocessing (in its constructor) and postprocessing (in its destructor), These tasks include synchronizing several streams, checking whether the stream is OK, and skipping whitespaces, as well as possibly implementation-specific tasks (For example, in a multithreaded environment, the additional processing might be used for corresponding locking). If an I/O operator operates directly on the stream buffer, a corresponding sentry object should be constructed first.
2) Detail definitions
namespace std {
template <typename charT, typename traits = char_traits<charT> >
class basic_ios;
template <typename charT, typename traits = char_traits<charT> >
class basic_streambuf;
template <typename charT, typename traits = char_traits<charT> >
class basic_istream;
template <typename charT, typename traits = char_traits<charT> >
class basic_ostream;
template <typename charT, typename traits = char_traits<charT> >
class basic_iostream;
}
3) Headers
The definitions of the stream classes are scattered among several header files. Only include <iosfwd> in your application header file. Only include the necessary header in your implementation file. Never include <iostream> if not necessary, coz it may cause performance issue(Executing the cin, cout code is not that expensive, but it requires loading the corresponding pages of the executable, which might be expensive).
- <iosfwd> contains forward declarations for the stream classes. This header file is necessary because it is no longer permissible to use a simple forward declaration, such as class ostream.
- <streambuf> contains the definitions for the stream buffer base class (basic_streambuf<>).
- <istream> contains the definitions for the classes that support input only (basic_istream<>) and for the classes that support both input and output (basic_iostream<>).
- <ostream> contains the definitions for the output stream class (basic_ostream<>).
- <iostream> contains declarations of the global stream objects, such as cin and cout.
4) Since C++11, concurrent output using the same stream object is possible but might result in interleaved characters
5) Since C++11, concurrent input using the same stream object is possible but might result in characters where it is not defined which thread reads which character
6) How Manipulators Work. We can define our own manipulators.
ostream& ostream::operator << ( ostream& (*op)(ostream&))
{
// call the function passed as parameter with this stream as the argument
return (*op)(*this);
}
For example:
template <typename charT, typename traits>
std::basic_ostream<charT,traits>&
std::endl (std::basic_ostream<charT,traits>& strm)
{
strm.put(strm.widen(’\n’));
strm.flush();
return strm;
}
7) If stream I/O is the performance bottleneck, use the stream buffer directly.
All member functions of the class basic_istream and basic_ostream that read or write characters operate according to the same schema. First, a corresponding sentry object is constructed, and then the operation is performed. The construction of the sentry object results in flushing of potentially tied objects, skipping of whitespace for input, and such implementation-specific operations as locking in multithreaded environments.
For unformatted I/O, most of the operations (in sentry) are normally useless anyway. Only locking operation might be useful if the streams are used in multithreaded environments. Thus, when doing unformatted I/O, it may be better to use stream buffers directly.
To support this behavior, you can use operators << and >> with stream buffers as follows:
- By passing a pointer to a stream buffer to operator <<, you can output all input of its device. This is probably the fastest way to copy files by using C++ I/O streams.
For example:
// copy all standard input to standard output
std::cout << std::cin.rdbuf();
- By passing a pointer to a stream buffer to operator >>, you can read directly into a stream buffer. For example, you could also copy all standard input to standard output in the following way:
// copy all standard input to standard output
std::cin >> std::noskipws >> std::cout.rdbuf();
Note that you have to clear the flag skipws. Otherwise, leading whitespace of the input is skipped.
Even for formatted I/O, it may be reasonable to use stream buffers directly. For example, if many numeric values are read in a loop, it is sufficient to construct just one sentry object that exists for the whole time the loop is executed. Then, within the loop, whitespace is skipped manually — using the ws manipulator would also construct a sentry object — and then the facet num_get is used for reading the numeric values directly.