Historically, binary mode is to provide more or less transparent access
to the underlying stream; text mode "normalizes" to a standard text
representation, where lines are terminated by the single '\n'
character. In addition, the system may impose restrictions on the size
of a binary file, for example by requiring it to be a multiple of 128 or
512 bytes. (The first was the case of CP/M, the second of many of the
DEC OS's.) Text files don't have this restriction, and in cases where
the OS imposed it, the library will typically introduce an additional
end of file character for text files. (Even today, most Windows
libraries recognize the old CP/M end of file, 0x1A, when reading in text
mode.) Because of these considertaions, text mode is only defined over
a limited set of binary values. (But if you write 200 bytes to a binary
file, you may get back 256 or 512 when you re-read it. Historically,
binary should only be used for text that is otherwise structured, so
that you can recognize the logical end, and ignore these additional
bytes.)
Also, you can seek pretty much arbitrarily in a file opened in binary
mode; you can only seek to the beginning, or to a position you've
previously memorized, in text mode. (This is because the line ending
mappings mean that there is no simple relationship between the position
in the file, and the position in the text stream.)
Note that this is orthogonal to whether the output is formatted or not:
if you output using <<
(and input using >>
), the IO is formatted,
regardless of the mode in which the file was opened. And the formatting
is always text; the iostreams are designed to manipulate streams of
text, and only have limited support for non-text input and output.
Today, the situation has changed somewhat: in many cases, we expect what
we write to be readable from other machines, which supposes a well
defined format, which may not be the format used natively. (Thus, for
example, the Internet expects the two byte sequence 0x0D, 0x0A as a line
ending, which is different than what is used internally in Unix and many
other OS's.) If portability is a concern, you generally define a
format, write it explicitly, and use binary mode to ensure that what you
write is exactly what is written; similarly on input, you use binary
format, and handle the conventions manually. If you're just writing to
a local disk, which isn't shared, however, text mode is fine, and a bit
less work.
Again, both of these apply to text. If you want a binary format, you
must use binary mode, but that's far from sufficient. You'll have to
implement all of the formatted IO yourself. In such cases, I generally
don't use std::istream
or std::ostream
(whose abstraction is text),
but rather define my own stream types, deriving from std::ios_base
(for the error handling conventions), and using std::streambuf
(for
the physical IO).
Finally, don't neglect the fact that all IO is formatted in some
manner. Just writing a block of memory out to the file means that the
format is whatever the current implementation happens to give you (which
is generally undocumented, which means that you probably won't be able
to read it in the future). If all you're doing is spilling to disk, and
the only time you'll read it is with the same program, compiled with the
same version of the same compiler, using the same compiler options, then
you can just dump memory, provided the memory in question is only PODs,
and contains no pointers. Otherwise, you have to define (and document)
the format you use, and implement it. In such cases, I'd suggest using
an existing format, like XDR, rather than inventing your own: it's a lot
easier to write "uses XDR format" as documentation, rather than
describing the actual bit and byte layout for all of the different
types.