Why do you deserve a better IO library
Hi!
In this article I'll try to summarize the reasons that lead me to
dislike IOStream as
it is now, and to hanker for a better IO library for C++. The arguments
are presented are
of two kinds:
- Cosmetic Problems: has to do with bad names, bad conventions, ...
etc. Basically, these
can be solved without major rework.
- Design Problems: are the fundamental problems with the design of
IOStream. Solving
these may mean coming up with a whole new library.
The arguments are numbred for easy reference.
Cosmetic Problems:
^^^^^^^^^^^^^^^^^^
1- Very complicated (and overlapping) state access functions, all with
strange names: rdstate(),
clear(), setstate().
2- fail() tests for both eofbit (which is a rather expected cause of
failure) and failbit (which
less expected). inorder test for fail alone you have to come up with
rdstate()&failbit.
4- Input operations set eofbit _and_ failbit on EOF, which makes it
impossible to make stream
classes throw only when operation fails because of stream error, and
not because of EOF.
3- The use of implicit convertion to void* (or to bool) does more harm
than good. It is not
really clear what are we testing for in 'if(cin) { ... }'
4- Unformatted input functions handle exceptions specially (I hate
special cases)
5- Format flag manipulation is unnecessarily complicated by strange
names (and too much overlap
in functionality): setf(f), setf(f, m), unsetf(f), flags(), flags(f),
(even with
io manipulators) setiosflags(f), resetiosflags()
6- All these basic_ prefixes look bad. Why don't we simply have
template versions named
std::stream<T=char> whith a default template parameter set to char?
7- To say nothing about members of streambuf: putback, unget get,
eback, gptr, egptr, setp,
pbase, pptr, gbump, uflow, snextc, sbumpc, sgetc, sputc, sputn,
showmanyc,
pbackfail... etc
Design Problems:
^^^^^^^^^^^^^^^^
1- The simple fact of #including <iostream> incur some overhead on the
generated code. Can you
bear it?
2- std::cout is an instance of std::ostream, and std::ostreams have a
seekp() member that allows
to traverse the character sequence. But it is meaningless to seekp()
forward with
std::cout. So seekp() isn't defined for the standard output? Ok! then
either:
- std::cout shouldn't be an instance of std::ostream, or
- std::ostream shouldn't contain a seekp() member
Which lead (respectively) to tow questions:
- std::cout should be an instance of which class then?
- Then where to put seekp()?
3- Uppon failure, IOStream (partiularily file stream classes) throw
instances of ios_base::failure.
But this isn't much of help. If try to open a file and get an
ios_base::failure, what
can you do? You can't output the result of the what() member because
its content is
undefined and surley not in the "right" natural language. Indeed, you
have absolutly no
idea of _what_ happend: file_not_found? permission_access_error?
bad_filename?
system_error? disk_error? would have been more informative things to
throw. Providing
an error report in the form of a simple character string (in some
arbitrary language) is
simply not enough.
5- The use of virtual inheritance is not justified in my opinion. Did
anyone ever handled a
stream throught a basic_ios<>* pointer? Aggregation would have been a
better option.
4- streambuf is a kind of "super class" that has members for everything
(input, output, seek in
both directions ... etc), even though most of its instances can't
actually support all
the operations.
-5 streambuf is actually mixing two compleatly unrelated concepts: The
concept of a buffer for
IO, and the concept of an external source/sink of data, which makes
extending the
library to support new kind of stream unnecessarily difficult.
Epilogue:
^^^^^^^^^
Design problems are (of course) more important than cosmetic ones. And
I think the most important
problem is the fact that instances don't support all the operations
exported by classes. For
example, if I write a prototype like:
void encrypt(std::istream&, std::ostream&);
Can encrypt() seek the streams? Given that it is declared to take
std::streams and that std::streams
export seek members, the answer would nomrally be 'yes'. But suppose
another person looks at
the prototype and asks himself "Can I pass std::cout as a second
argument?", "Given
that std::cout is an std::ostream (as required by the prototype)....."
you get the idea. This
kind of problem shouldn't exist, otherwise why are we bothering with
classes and OO?
I'd be glad to hear you opinion.
P.S.: It would be nice if every one said with which points (as numbered
above) he agrees (in
addition to those with which he disagrees).
Yours,
Anis Benyelloul
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]