+1  A: 

(This answer is just based on my opinion)

I think that IOStreams are much more complex than their function equivalents. When I write in C++, I still use the cstdio headers for "old-style" I/O, which I find much more predictable. On a side note, (though it isn't really important; the absolute time difference is negligible) IOStreams have been proven on numerous occasions to be slower than C I/O.

Delan Azabani
I think you mean "function" rather than "functional". functional programming produces code thats even worse looking that generic programming.
Chris Becke
Thanks for pointing that mistake out; I've edited the answer to reflect the correction.
Delan Azabani
IOStreams would almost certainly have to be slower than classic stdio; if I was given the task to design an extensible and easy-to-use I/O streams framework, I would probably judge speed secondary, given that the real bottlenecks will likely be file I/O speed or network traffic bandwidth.
stakx
I agree that for I/O or network the computational speed does not matter that much. However remember that the C++ for numeric/string conversion is using `sstringstream`. I think speed does matter, though it's secondary.
Matthieu M.
@Matthieu M.: Most of the time you don't need to do the kinds of conversions you use stringstream for unless you are doing i/o anyway.
Billy ONeal
+17  A: 

Regarding who designed them, the original library was (not surprisingly) created by Bjarne Stroustrup, and then reimplemented by Dave Preston. This was then redesigned and reimplemented yet again by Jerry Schwartz for Cfront 2.0, using the idea of manipulators from Andrew Koenig. The standard version of the library is based on this implementation.

Source "The Design & Evolution of C++", section 8.3.1.

anon
@Neil - nut what's your opinion of the design? Based on your other answers, many people would love to hear your opinion...
DVK
@DVK Just posted my opinion as a separate answer.
anon
Just found a transcript of an interview with Bjarne Stroustrup where he mentions some bits and pieces of IOStreams history: http://www2.research.att.com/~bs/01chinese.html (this link seems to be temporarily broken right now, but you can try Google's page cache)
stakx
+9  A: 

Several ill-conceived ideas found their way into the standard: auto_ptr, vector<bool>, valarray and export, just to name a few. So I wouldn't take the presence of IOStreams necessarily as a sign of quality design.

IOStreams have a checkered history. They are actually a reworking of an earlier streams library, but were authored at a time when many of today's C++ idioms didn't exist, so the designers didn't have the benefit of hindsight. One issue that only became apparent over time was that it is almost impossible to implement IOStreams as efficiently as C's stdio, due to the copious use of virtual functions and forwarding to internal buffer objects at even the finest granularity, and also thanks to some inscrutable strangeness in the way locales are defined and implemented. My memory of this is quite fuzzy, I'll admit; I remember it being the subject of intense debate some years ago, on comp.lang.c++.moderated.

Marcelo Cantos
Thank you for your input. I'll browse the `comp.lang.c++.moderated` archive and post links at the bottom of my question if I find something valuable. -- Besides, I dare disagree with you on `auto_ptr`: After reading Herb Sutter's _Exceptional C++_ it seems like a very useful class when implementing the RAII pattern.
stakx
@stakx: Nevertheless it is getting deprecated and superseded by `unique_ptr` with clearer and more powerful semantics.
UncleBens
@UncleBens: Thanks for that pointer to `unique_ptr`. I am continually amazed at what the Boost libraries come up with. (Unfortunately I'm still not nearly as familiar with them as I'd like to be.)
stakx
@UncleBens `unique_ptr` requires rvalue reference. So at this point `auto_ptr` is very powerful pointer.
Artyom
But `auto_ptr` has screwed copy/assignment semantics which make it a niche for dereferencing bugs...
Matthieu M.
@Matthieu M.: That said, it's the best solution we have under the current standard. It's not poorly designed -- you just have to be aware of how it operates.
Billy ONeal
@Billy: I would have prefer `boost::scoped_ptr` semantics, I prefer not to have a copy constructor if it doesn't behave like a copy constructor. You can always use `swap` to exchange (explicitly) the contents of two of them, or another aptly named method.
Matthieu M.
@Matthieu: Unless you're wrapping a polymorphic interface you're using to fake an Operating System dependency. Then you cannot use anything like `scoped_ptr`. I Agree that `auto_ptr`'s semantics are strange. That doesn't mean it's designed poorly though.
Billy ONeal
what's so terrible about `vector<bool>`?
TokenMacGuy
Marcelo Cantos
@TokenMacGuy: it's not a vector, and it doesn't store bools. Which makes it somewhat misleading. ;)
jalf
@TokenMacGuy it is required to be bitpacked. And this in turn means that not every entry in it has a distinct memory address, so you can't give out a normal reference to a contained bool. Vector<bool> should'nt have been bitpacked, but instead a special class for BitPackedVectors should have been created.
CodeInChaos
+10  A: 

I'm posting this as a separate answer because it is pure opinion.

Performing input & output (particularly input) is a very, very hard problem, so not surprisingly the iostreams library is full of bodges and things that with perfect hindsight could have been done better. But it seems to me that all I/O libraries, in whatever language are like this. I've never used a programming language where the I/O system was a thing of beauty that made me stand in awe of its designer. The iostreams library does have advantages, particularly over the C I/O library (extensibility, type-safety etc.), but I don't think anyone is holding it up as an example of great OO or generic design.

anon
+5  A: 

i always found C++ IOStreams ill-designed: their implementation makes it very difficult to properly define a new type a stream. they also mix io features and formatting features (think about manipulators).

personally, the best stream design and implementation i have ever found lies in the Ada programming language. it is a model in decoupling, a joy to create new type of streams, and output functions always work regardless of the stream used. this is thank to a least common denominator: you output bytes to a stream and that's it. stream functions take care of putting the bytes into the stream, it is not their job to e.g. format an integer into hexadecimal (of course, there is a set of type attributes, equivalent to a class member, defined for handling formatting)

i wish C++ was as simple regarding to streams...

Adrien Plisson
The book I mentioned explains the basic IOStreams architecture as follows: There is a _transport layer_ (the stream buffer classes) and a _parsing/formatting layer_ (the stream classes). The former are responsible for reading/writing characters from/to a bytestream, while the latter are responsible for parsing characters or serializing values into characters. This seems clear enough, but I'm not sure if these concerns are truly clearly separated in reality, esp. when locales come into play. -- I also agree with you on the difficulty of implementing new streams classes.
stakx
" mix io features and formatting features" <-- What's wrong about that? That's kind of the point of the library. Regarding making new streams, you should make a streambuf instead of a stream and construct a plain stream around the streambuf.
Billy ONeal
it seems answers to this question made me understand something that i was never explained: i should derive a streambuf instead of a stream...
Adrien Plisson
+6  A: 

I think IOStreams design is brilliant in terms of extendability and usefulness.

  1. Stream buffers: take a look on boost.iostream extensions: create gzip, tee, copy streams in few lines, create special filters and so on. It would not be possible without it.
  2. Localization integration and formatting integration. See what can be done:

    std::cout << as::spellout << 100 << std::endl;
    

    Can print: "one hundred" or even:

    std::cout << translate("Good morning")  << std::endl;
    

    Can print "Bonjour" or "בוקר טוב" according to the locale imbued to std::cout!

    Such things can be done just because iostreams are very flexible.

Could it be done better?

Of course it could! In fact there are many things that could be improved...

Today it is quite painful to derive correctly from stream_buffer, it is quite non-trivial to add additional formatting information to stream, but possible.

But looking back many years ago I still the library design was good enough to be about to bring many goodies.

Because you can't always see the big picture, but if you leave points for extensions it gives you much better abilities even in points you didn't think about.

Artyom
Can you provide a comment as to why your examples for point 2 would be better than simply using something like `print (spellout(100));` and `print (translate("Good morning"));`This would seem like a good idea, as this decouples formatting and i18n from I/O.
Schedler
Because it can be translated according to langauage imbued into stream. i.e.: `french_output << translate("Good morning")` ; `english_output << translate("Good morning")` would give you: "Bonjour Good morning"
Artyom
Localisation is much harder when you need to do '<<"text"<<value' in one language but '<<value<<"text" ' in another - compared to printf
Martin Beckett
@Martin Beckett I know, take a look on Boost.Locale library, what happens that in such case you do `out << format("text {1}") % value` and it may be translated to `"{1} translated"`. So it works fine `;-)` .
Artyom
What "can be done" isn't very relevant. You're a programmer, anything *can be done* with enough effort. But IOStreams makes it awfully painful to achieve most of what *can be done*. And you usually get lousy performance for your trouble.
jalf
+6  A: 

If you had to judge by today's software engineering standards (if there actually is any general agreement on these), would C++'s IOStreams still be considered well-designed? (I wouldn't want to improve my software design skills from something that's generally considered outdated.)

I would say NO, for several reasons:

Poor error handling

Error conditions should be reported with exceptions, not with operator void*.

The "zombie object" anti-pattern is what causes bugs like these.

Poor separation between formatting and I/O

This makes stream objects unnecessary complex, as they have to contain extra state information for formatting, whether you need it or not.

It also increases the odds of writing bugs like:

using namespace std; // I'm lazy.
cout << hex << setw(8) << setfill('0') << x << endl;
// Oops!  Forgot to set the stream back to decimal mode.

If instead, you wrote something like:

cout << pad(to_hex(x), 8, '0') << endl;

There would be no formatting-related state bits, and no problem.

Note that in "modern" languages like Java, C#, and Python, all objects have a toString/ToString/__str__ function that is called by the I/O routines. AFAIK, only C++ does it the other way around by using stringstream as the standard way of converting to a string.

Poor support for i18n

Iostream-based output splits string literals into pieces.

cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;

Format strings put whole sentences into string literals.

printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);

The latter approach is easier to adapt to internationalization libraries like GNU gettext, because the use of whole sentences provides more context for the translators. If your string formatting routine supports re-ordering (like the POSIX $ printf parameters), then it also better handles differences in word order between languages.

dan04
Actually, for i18n, replacements should be identified by positions (%1, %2, ..), as a translation may require to change parameter order. Otherwise, I fully agree - +1.
peterchen
@peterchen: That's what the POSIX `$` specifiers for `printf` are.
jamesdlin
@dan04: Regarding your last point about %s-style printing, it suffers from the same problem as printf does, that is, it's inherently type unsafe. This isn't as much of a problem in Python, which uses Duck Typing extensively, and in Java or C#, where the environment can catch type errors at runtime.
Lie Ryan
The problem isn't format strings, it's that C++ has non-typesafe varargs.
dan04
+2  A: 

My opinion of C++ iostreams has improved substantially over time, particularly after I started to actually extend them by implementing my own stream classes. I began to appreciate the extensibility and overall design, despite the ridiculously poor member function names like xsputn or whatever. Regardless, I think I/O streams are a massive improvement over C stdio.h, which has no type safety and is riddled with major security flaws.

I think the main problem with IO streams is that they conflate two related but somewhat orthogonal concepts: textual formatting and serialization. On the one hand, IO streams are designed to produce a human-readable, formatted textual representation of an object, and on the other hand, to serialize an object into a portable format. Sometimes these two goals are one and the same, but other times this results in some seriously annoying incongruities. For example:

std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;

...

std::string input_string;
ss >> input_string;
std::cout << input_string;

Here, what we get as input is not what we originally outputted to the stream. This is because the << operator outputs the entire string, whereas the >> operator will only read from the stream until it encounters a whitespace character, since there's no length information stored in the stream. So even though we output a string object containing "hello world", we're only going to input a string object containing "hello". So while the stream has served its purpose as a formatting facility, it has failed to properly serialize and then unserialize the object.

You might say that IO streams weren't designed to be serialization facilities, but if that's the case, what are input streams really for? Besides, in practice I/O streams are often used to serialize objects, because there are no other standard serialization facilities. Consider boost::date_time or boost::numeric::ublas::matrix, where if you output a matrix object with the << operator, you'll get the same exact matrix when you input it using the >> operator. But in order to accomplish this, the Boost designers had to store column count and row count information as textual data in the output, which compromises the actual human-readable display. Again, an awkward combination of textual formatting facilities and serialization.

Note how most other languages separate these two facilities. In Java, for example, formatting is accomplished through the toString() method, while serialization is accomplished through the Serializable interface.

In my opinion, the best solution would have been to introduce byte based streams, alongside the standard character based streams. These streams would operate on binary data, with no concern for human-readable formatting/display. They could be used solely as serialization/deserialization facilities, to translate C++ objects into portable byte sequences.

Charles Salvia
stakx
@stakx, yes, and in fact, I have done this. It's slightly more annoying than it sounds, since `std::char_traits` can't be portably specialized to take an `unsigned char`. However, there are workarounds, so I guess extensibility comes to the rescue once again. But I think the fact that byte-based streams aren't standard is a weakness of the library.
Charles Salvia
Also, implementing binary streams requires you to implement new stream classes *and* new buffer classes, since formatting concerns aren't entirely separated from `std::streambuf`. So, basically the only thing you're extending is the `std::basic_ios` class. So there's a line where "extending" crosses over into "completely reimplementing" territory, and creating a binary stream from the C++ I/O stream facilities seems to approach that point.
Charles Salvia
stakx