C++ streams suck

18.01.2008 20:19

My work at Zemanta in the last two weeks included optimizing performance of some core software components. This mostly means moving stuff from Python to C++ which also means that I've been exposed to more C++ in the last week than in five years at the Faculty (where I tried to stay away from it as much as possible except for a few odd KDE hacks back when I still used it).

Anyway, IO streams always seemed awkward to me. But that's usually the case when you start using a language you're not fluent in. So I tried to go on with them and waited for the enlightenment when I would say "Oh! I don't know how I ever lived with the plain old stdio.h". Well, today I finally got it, but it was the other way around. I'm moments from making it a policy that all my C++ code will strictly use the plain old stdio.h for console and file I/O.

Why? Well, consider the following example. I want to make a string that will hold a hex representation of a 32 bit integer. It's going to be read by another program so the format must be pretty strict. Using the C++ way, you would do that like so:

stringstream g;
g << "0x" << hex << setw(8) << setfill('0') << i << endl;

It's long and relatively unreadable. Add a couple more manipulators and you could easily have a statement that prints 10 characters span multiple lines. Compare this with C equivalent:

printf("0x%08x\n", i);

Ok, maybe I'm biased regarding readability. However compare what these two programs print out:

# C++ version
0x000b,eaf
# C version
0x0000beaf

Why is there a comma in the middle? I just spent an hour debugging this earlier today. The cause? Well the comma is a thousands separator from the current locale which the stream library helpfully inserts in your string. Yes, a thousands separator that actually separates four thousand and ninety sixths. A solution? Adding another string manipulator that modifies the locale (a manipulator that just turns off the thousands separator doesn't actually exist). Just beautiful.

The fun doesn't stop there. I spent another hour today debugging some piece of code that opens a file. In the end it turned out that the open call was failing because I tried to open a file that was a couple of megabytes over the 2 GB limit and the program wasn't compiled with support for large files. I don't mind that, it was my mistake. However I do mind that it took me as long as it did to find the cause.

The fstream objects just silently fail when there is something wrong. The damn things don't even throw exceptions by default. Isn't that the C++ way of handling errors? Instead they set some hidden state you have to specially check and even then you can only get the message that something failed, not what specifically went wrong. Add to that the horrible mess that is STL documentation and you see why I only found out what was causing the program to crash when I dumped fstreams and rewrote the whole thing to use stdio.h functions.

So, in conclusion, C++ itself is pretty nice as a language (and blindingly fast compared to Python, which has the speed of a snail nailed to the table) but the standard infrastructure that comes with it causes more problems than it solves. Instead of using it and waiting to find out the bright sides of it, I'll use the old way I know from C and wait until someone else shares his enlightenment with me.

Posted by Tomaž | Categories: Code

Comments

The second criticism you make, regarding error handling, is pretty valid. Although, there is an ios::exceptions() function you use to force stream objects to throw exceptions on failure.

But the reason C++ streams are designed the way they are is mostly for extensibility. The streams infrastructure includes facet, locale, and buffering components which can be extended to create any kind of stream. I've done this multiple times to create socket streams, pipe streams, binary streams, etc., and it all works nicely with the existing infrastructure. You can't really do that with C stdio without rewriting everything. And of course, the stream input/output operators can be overloaded so that user-designed classes can be outputted through streams - another thing that is not possible with C.

Posted by Charles Salvia

Hello,

I got rid of the thousands separator using a custom numpunct facet like this:

template<typename charT>
struct suppress_thousand_separator : public std::numpunct_byname<charT>
{
typedef std::basic_string<charT> string_type;
explicit suppress_thousand_separator (const char* name, size_t refs=0)
: std::numpunct_byname<charT> (name, refs)
{
}
protected:
// This function overrides the one from the base class.
// Returning an empty string it just disable grouping
// for the thousands, so no thousands separator will
// be ever outputted. The same thing is done by the C
// locale.
virtual string do_grouping() const
{
return string ("");
}
};//struct

I would like to see the manipulator you wrote to eliminate those nasty thousands separator, i would be grateful thanks!

Greetings,
Luca

Luca, I ended up using this:

stringstream g;

g.imbue(locale::classic());

g << std::hex << std::noshowpoint;

Posted by Tomaž

Also, if you are "allowed" to use Boost, see Boost::format and friends. The poster above is right, though, C++ streams-- like many things-- often have purposes and decisions made on their design that may not be "obvious" for a given use case.

Posted by Jesse

Add a new comment


(No HTML tags allowed. Separate paragraphs with a blank line.)