Topic: Input / Output Iterator Error Handling
Author: "David Abrahams" <abrahams@mediaone.net>
Date: Wed, 16 May 2001 20:43:21 GMT Raw View
In article <949qf3$l3u$1@nnrp1.deja.com>,
James.Kanze@dresdner-bank.com wrote:
> > > > > This has nothing to do with exceptions, as such. I'd be just
> > > > > as upset if the C standard said that an implementation could
> > > > > report errors not specified in the standard, and that the
> > > > > return type of the functions which might report an error was
> > > > > implementation defined.
>
> > > > Are you certain that it doesn't?
>
> > > Yes. The return type is fully specified for every function in the
> > > library. The only exception I can think of is resource limits,
> > > and even there, the C standard generally says what the function
> > > should do. Even if one accepts that the general clause concerning
> > > resource limits has precedence over the specific clauses
> > > concerning error reporting, the intent is clear enough that
> > > quality of implementation can do the rest.
>
> > I wasn't really asking about the return type; I was asking whether
> > an implementation is allowed to report implementation-defined
> > errors?
>
> It is, via errno.
Then 'C' library error reporting is no different than C++ library error
reporting in that each can report arbitrary implementation-defined errors of
the implementor's choosing. The fact that C++ can throw different types is
not a safety issue: a portable 'C' program doesn't know what to do with an
implementation-defined errno value any better than a C++ program knows how
to report errors in a catch(...) clause
-Dave
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "David Abrahams" <abrahams@mediaone.net>
Date: Fri, 19 Jan 2001 02:40:22 GMT Raw View
In article <9444ks$oat$1@nnrp1.deja.com>,
James.Kanze@dresdner-bank.com wrote:
>
> I'm just trying to find out what is and what isn't guaranteed. And
> you seem to be the only one who really knows what the standard says in
> this regard.
>
> [...]
> > > - Finally, I guess one could interpret this to mean that the
> > > function throws no exceptions itself, but doesn't catch any
> > > either, so exceptions from operator>> (from istream or from
> > > the object being read) will percolate up. This is the
> > > interpretation I would like, but it takes a lot of imagination
> > > to find it in the standard.
>
> > That's because it's not what it says.
>
> A good question might be: why not? Why should an istream_iterator be
> allowed to generate new exceptions? Why should it be allowed to, say,
> throw a C style string (char const*) if it detects end of file? I can
> easily see what this freedom costs us; I can't see what we gain by it.
I think library implementors like a lot of arbitrary leeway to
innovate. I am speculating here, but I think that's why the blanket
allowance is made for the library to throw arbitrary exceptions. It
might be easy to get restrictions for this particular class template,
but I suspect that convincing the implementors on the committee to give
up the general leeway would be much more difficult. Personally, I don't
see a restriction for one class template as particularly valuable.
> > > Are you saying that e.g. the following implementation is legal:
>
> > > istream_iterator<T>&
> > > istream_iterator<T>::operator++()
> > > {
> > > throw "I don't like you" ;
> > > }
>
> > I don't think so; this doesn't have the required semantics for
> > ++. The standard says "every time ++ is used, the iterator reads and
> > stores a value of T".
>
> It obviously has a very original definition of "failure". But if the
> standard basically says that an implementation can throw any exception
> it pleases, for any reason it pleases, how does this fail to conform.
It's borderline QOI, but you could say that it fails to conform because
it never, under any circumstances, has the required semantics.
> In your opinion, would it be conform if, after doing whatever it had
> to do, it ended in:
>
> if ( ! *in_stream ) throw "Too bad" ;
>
> ?
Yes.
> > > If, instead of istream_iterator, it were a user defined iterator,
> > > I would argue that this IS legal.
>
> > This is borderline. Can an iterator with this implementation fulfill
> > the input iterator requirements in Table 72?
>
> Yes and no. Table 72 doesn't say what the behavior of an iterator is
> in case of failure. An obvious case is a trivial iterator, which
> iterates over an always empty sequence. In this case, the
> precondition for ++ is never met, and so it is guaranteed that the
> function will never be called.
Okay, yeah. In that case, it's certainly a legal implementation.
> More generally, however, what happens
> if the operator ++ needs more memory, and there isn't any? Nothing in
> table 72 says that it cannot throw bad_alloc, but I think we would
> both agree that that is the intent, or at least the preferred
> behavior.
I don't agree that it's the intent of Table 72 to prohibit throwing
(where does it even mention exceptions?), and I guess I don't see the
relevance of "preferred behavior" in this context. I can imagine lots of
useful iterators that might need to allocate memory in operator++(), and
an exception seems to me like the only reasonable way to report that
failure.
> It's interesting to note that the text of 17.4.4.8 only applies to the
> implementation, not to user classes. If the text of 17.4.4.8 has
> precedence over table 72, and table 72 is interpreted to forbid
> throwing (which I don't think it should be),
I thought you were just arguing the opposite (?)
> then we have the case
> where the standard library has the right to throw for no reason at
> all, but the user code doesn't.
But we don't have that case, since Table 72 doesn't prohibit throwing.
> Just curious: given the following code at the start of an operator++:
>
> char* p = malloc( 1000000000 ) ; // That's 10^9, 1 Gigabyte.
> if ( p == NULL ) throw "Out of memory" ;
>
> Is this legal in a user defined iterator? In one of the iterators in
> the library? (I think the answer is yes to both.
Except for the standard container iterators, that's correct (see 23.1
paragraph 10). Note that only "returned" iterators are forbidden to
throw, so default-constructed iterators are not guaranteed copiable
without exceptions.
> Note that on the
> machines I run on, this is effectively the equivalent of an iterator
> whose operator++ just contains the single throw statement.)
Just as legal as your std::streambuf implementation, which for some
sufficiently small platform memory size will always throw in its
constructor.
> > > Useless, of course. But legal. I would hope that it isn't legal
> > > for istream_iterator. Which means that istream_iterator does
> > > offer additional guarantees.
>
> > Only that it must try to do what the standard says it must try to
> > do. The standard doesn't say /how/ it must try to do that, which
> > doesn't rule out ways that may throw an exception.
>
> So it all boils down to quality of implementation.
As do lots of things in the standard; many more than I'd like.
> On further
> thought, I guess it is acceptable, since usefulness really does boil
> down to quality of implementation. When it comes down to it, it's no
> more useless than an implementation that generates a five minute empty
> loop after each source instruction, and the standard doesn't forbid
> that either. (The example is from Steve Clamage, as to why the
> standard cannot guarantee usefulness.) I do wish that the intent (if
> not the actual requirements) concerning exceptions had been a bit more
> clear, however.
Unfortunately, the standard isn't organized for clarity of intent, and
at the late stage when the exception-safety guarantees were introduced,
it had to be organized for "least number of words changed", which was
especially tricky. Greg Colvin skillfully navigated those waters when we
drafted the standard language.
> > > If, as
> > > a user, I initialize an istream not to throw, and I instantiate
> > > istream_iterator for a type whose operator>> doesn't throw, I
> > > would expect to be able to write my code confident that other than
> > > a possible std::bad_alloc due to buffer allocation, all errors
> > > will be reported via the state bits of the istream.
>
> > Why do you think bad_alloc has special status? I think you're making
> > more assumptions about the implementation of the standard library
> > than you have a right to. For better or worse, we all do this when
> > we program (heck, I use a standard lib implementation which I help
> > to maintain), but it's part of the struggle of writing portable code
> > to be "consciously ignorant" about these things.
>
> Just how far can you afford to be ignorant? At some point, you have
> to be able to distinguish between normal conditions (an input failed
> because of end of stream),
You can always check eofbit...
> and abnormal, perhaps fatal errors (format
> error in the input stream, insufficient memory, etc.). Any decent
> implementation will provide for this, but unless they all provide for
> it in the same way, it is useless for portable code.
...but I agree with you that more uniformity of exceptions would be
helpful for users.
> Some things are probably (certainly?) safe, regardless of the
> liberties given by the standard. If the reason for failure is
> insufficient memory, I expect bad_alloc, or something derived from
> bad_alloc. Regardless of where the error occurs in the library.
I am not sure how the standard rules on this. I is not completely
portable in practice on implementations in use today (e.g. VC6's
operator new never throws).
> If operator>> fails, I expect istream_iterator 1) to pass any
> exceptions on, unchanged, and 2) if no exceptions are thrown, to
> detect the error in istream (failbit, etc.), to become effectively a
> "one past the end" iterator,
Does it do that? 24.5.1 paragraph 1 seems to indicate that only happens
when eofbit is set.
> and to do *nothing* else.
>
> Is it reasonable to expect all of this, as a quality of implementation
> issue?
As a QOI issue, I don't know; I try not to make assumptions in that
space. I don't think I'm a good arbiter of what's reasonable to expect
there. You probably should ask some implementors [but I wouldn't expect
agreement ;-(].
> > > If istream_iterator converts ios::fail to an exception of type
> > > char const*, for example, my code will break.
>
> > > You don't really mean to say that an implementor has that much
> > > freedom, do you.
>
> > I'm afraid so.
>
> So how do you write portable code? Count on all implementations just
> happening to do the right thing?
I write code for which depends as little as possible on the type of
exception thrown for its correctness. Usually, the only place where this
matters is in code that prepares error reports for the user. At that
point, so far, I've been able to collect all the exception types I'm
likely to have to handle so I can decode them in catch clauses. Yes,
this function will have to be adjusted for different implementations,
but since the implementation is required to define the additional
exceptions it may throw, it's not too bad.
I'll admit that I'm still struggling on how to handle exceptions from
standard streams, since the standard does such a poor job of supplying
relevant information.
> > > I don't think I have a problem with 17.4.4.8 directly. I have a
> > > problem with it because most of the standard library functions
> > > don't have Throws: clauses, and most don't have exception
> > > specifications. And the obvious interpretation is that in such
> > > cases, the function can throw anything, anytime.
>
> > Yep. That's old news. I know it's come up in conversations you and I
> > have had about whether to use exceptions or not.
>
> But most of the time, to date, the issue has really been whether it
> throws or not. We were concerned with exception safety in the
> intermediate code. We (or at least I) assumed that somewhere up
> above, there was code which knew which exceptions could be thrown
> (globally), and knew what to do when they were thrown. In this case,
> we have the problem that no code, anywhere in the system, knows what
> can occur. About all I can do is catch(...) and abort.
...or catch(...), report "unidentifiable error", and recover the same
way you would for bad_alloc.
Almost any interesting portable program has a few sections of
platform-specific code. It's not a /huge/ deal to look at the
implementation specification and write the appropriate catch clauses, is
it?
> > > I'd much rather see something to the effect that a library
> > > function cannot *generate* an exception itself, unless it has a
> > > Throws clause which specifies what exception it generates and
> > > under what conditions.
>
> > I'm not sure whether I'd like that, but I might, if we made sure to
> > get the neccessary Throws caluse on all functions which may need to
> > allocate resources.
>
> I'd accept a global statement to the effect that any standard function
> may allocate memory (and thus throw bad_alloc)
...or anything derived from bad_alloc? Are you intending to prohibit
standard functions from doing things other than allocating memory which
might throw? Suppose an implementation wanted to sort a huge array by
writing to a file when there wasn't enough memory (ok, bad example, but
I hope you get the point)?
> unless otherwise specified.
Already covered by footnote 178, but I guess you're proposing to
eliminate part of 17.4.4.8 paragraph 3, which would require elevating
the footnote to normative text.
> Provided, of course, that operator delete, malloc and free
> were otherwise specified. And ideally, a few other very basic
> functions, mostly inherited from C, like memcpy.
Already done for delete. Bjarne has made the contention to me that the
standard 'C' library functions are allowed to throw in C++, but I don't
know whether to believe him ;-).
> For the rest, I do think a guarantee that e.g. vector<> won't catch an
> exception from my constructor, and rethrow a char const*, certainly
> wouldn't hurt.
Exception-neutrality again. Some vector functions almost require that,
e.g. vector::erase(), which says "Throws: Nothing unless an exception is
thrown by the copy constructor or assignment operator of T." But I
guess there's nothing here to prevent vector from translating the
exception. Change it to "Throws: Nothing other than exceptions thrown by
the copy constructor or assignment operator of T" and you're done. With
this one function ;-)
> I suspect that, as always, getting the proper wording would be less
> than trivial.
It's a project. We're only just about to begin discussing extensions,
which this (clearly?) would be. I'm willing to help if you want to
propose new wording.
> I do want it. I want a lot of other things to, but this one seems
> fairly important if you want to write robust code.
As usual, it depends on your definition of "robust". I think the current
guarantees place the minimal requirements on the library neccessary to
write robust code for some definition of robust.
> > > So that exceptions from user defined instantiation classes are
> > > guaranteed to propagate up to be visible.
>
> > What's an instantiation class?
>
> The class over which a template is instantiated. The T in vector<T>,
> for example. (I think it's a word I just made up.)
It would be interesting to see if there are any arguments against
this. I can't think of any, but someone probably will.
> > > What this section seems to be saying is that if you want portable,
> > > robust code, you cannot use the standard library, because there is
> > > no way of knowing what functions may generate what errors, nor how
> > > the errors will be reported.
>
> > I wouldn't draw that conclusion. You know that all errors are
> > reported via exceptions, except as otherwise specified
> > (e.g. iostreams). You know that unless otherwise specified, any
> > standard library function may report an error. That's enough. I'm
> > not sure it's optimal, but it's certainly enough.
>
> If I know that the errors are all "fatal", and not informative.
No, the implementation is required to define (i.e. document) the
exceptions that can be thrown from functions without throws:
clauses. Interesting question, though: does this mean that all existing
implementations of vector<>::insert() are non-conforming because they
fail to explicitly mention that bad_alloc may be thrown? I don't think
footnote 178 gets an implementation off the hook for this.
> > > This has nothing to do with exceptions, as such. I'd be just as
> > > upset if the C standard said that an implementation could report
> > > errors not specified in the standard, and that the return type of
> > > the functions which might report an error was implementation
> > > defined.
>
> > Are you certain that it doesn't?
>
> Yes. The return type is fully specified for every function in the
> library. The only exception I can think of is resource limits, and
> even there, the C standard generally says what the function should do.
> Even if one accepts that the general clause concerning resource limits
> has precedence over the specific clauses concerning error reporting,
> the intent is clear enough that quality of implementation can do the
> rest.
I wasn't really asking about the return type; I was asking whether an
implementation is allowed to report implementation-defined errors?
> Perhaps the problem here is that even the intent is not clear enough.
The intent of what? The whole standard? Remember that it's created by a
patchwork of intents, sometimes working at cross-purposes
(e.g. supporting implementor leeway and also portability).
> > Sorry, James. 17.4.4.8 has been that way for many years, long before
> > I came on the scene. I've been warning people almost since then;
> > certainly since GotW#4 in April '97. Herb Sutter picked up the "you
> > can't really avoid exceptions because the standard library throws
> > them" mantra pretty soon thereafter.
>
> I missed this. I always thought that he meant things like bad_alloc
> -- if you use vector<>, for example, you must be prepared for at least
> a bad_alloc exception.
In practical terms, that's probably all you have to worry about.
> It never occured to me that vector might
> decide to trap this exception, and throw a double in its place. And
> I'd never read this particular section before.
>
> > > And all code that I've ever seen of anyone elses.
>
> > You haven't been looking at very many other peoples' code then. I
> > have certainly sent you code which isn't broken by 17.4.4.8. Are you
> > exaggerating or am I misunderstanding you?
>
> Strictly speaking, most of the code I've seen on the net simply
> ignores all possibilities of errors. So 17.4.4.8 doesn't break it any
> more than it was already broken. But most robust code I've seen does
> try and do something when errors are detected, what it does depends on
> the type of error, and 17.4.4.8, taken literally, breaks it.
I believe that this is true of complete programs, but lots of robust
library code doesn't need to concern itself with the reasons for a
failure.
>
> Anyway, your comments have been a great help in my understanding. I'd
> like to thank you for taking the time to address my concerns.
Glad to help. It's not a perfect world, by any stretch, but it's one I
(and, I hope, others) can live with.
Regards,
Dave
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: James.Kanze@dresdner-bank.com
Date: Fri, 19 Jan 2001 17:17:41 GMT Raw View
In article <036a01c081ba$9b048a00$0500a8c0@dragonsys.com>,
"David Abrahams" <abrahams@mediaone.net> wrote:
> In article <9444ks$oat$1@nnrp1.deja.com>,
> James.Kanze@dresdner-bank.com wrote:
[...]
> > If operator>> fails, I expect istream_iterator 1) to pass any
> > exceptions on, unchanged, and 2) if no exceptions are thrown, to
> > detect the error in istream (failbit, etc.), to become effectively
> > a "one past the end" iterator,
> Does it do that? 24.5.1 paragraph 1 seems to indicate that only
> happens when eofbit is set.
Does it? I read "If the end of stream is reached ( operator void*()
on the stream returns false), the iterator becomes equal to the
end-of-stream iterator value." The text in parentheses is important,
because the standard doesn't define "end of stream" elsewhere (that I
know of). istream::operator void*() doesn't consider eofbit: it
returns false (a null pointer) on badbit||failbit.
Otherwise, of course, an istream_iterator<double> would trigger an
endless loop on the first format error, since eofbit wouldn't be set,
and the iterator would never become equal to an end-of-stream
iterator.
[...]
> > > > This has nothing to do with exceptions, as such. I'd be just
> > > > as upset if the C standard said that an implementation could
> > > > report errors not specified in the standard, and that the
> > > > return type of the functions which might report an error was
> > > > implementation defined.
> > > Are you certain that it doesn't?
> > Yes. The return type is fully specified for every function in the
> > library. The only exception I can think of is resource limits,
> > and even there, the C standard generally says what the function
> > should do. Even if one accepts that the general clause concerning
> > resource limits has precedence over the specific clauses
> > concerning error reporting, the intent is clear enough that
> > quality of implementation can do the rest.
> I wasn't really asking about the return type; I was asking whether
> an implementation is allowed to report implementation-defined
> errors?
It is, via errno.
--
James Kanze mailto:kanze@gabi-soft.de
Conseils en informatique orient e objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelh ttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
Sent via Deja.com
http://www.deja.com/
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "David Abrahams" <abrahams@mediaone.net>
Date: Sun, 21 Jan 2001 06:05:06 GMT Raw View
In article <949qf3$l3u$1@nnrp1.deja.com>,
James.Kanze@dresdner-bank.com wrote:
> > > > > This has nothing to do with exceptions, as such. I'd be just
> > > > > as upset if the C standard said that an implementation could
> > > > > report errors not specified in the standard, and that the
> > > > > return type of the functions which might report an error was
> > > > > implementation defined.
>
> > > > Are you certain that it doesn't?
>
> > > Yes. The return type is fully specified for every function in the
> > > library. The only exception I can think of is resource limits,
> > > and even there, the C standard generally says what the function
> > > should do. Even if one accepts that the general clause concerning
> > > resource limits has precedence over the specific clauses
> > > concerning error reporting, the intent is clear enough that
> > > quality of implementation can do the rest.
>
> > I wasn't really asking about the return type; I was asking whether
> > an implementation is allowed to report implementation-defined
> > errors?
>
> It is, via errno.
Then 'C' library error reporting is no different than C++ library error
reporting in that each can report arbitrary implementation-defined errors of
the implementor's choosing. The fact that C++ can throw different types is
not a safety issue: a portable 'C' program doesn't know what to do with an
implementation-defined errno value any better than a C++ program knows how
to report errors in a catch(...) clause
-Dave
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: kuehl@ramsen.informatik.uni-konstanz.de (Dietmar Kuehl)
Date: Fri, 12 Jan 2001 17:51:47 GMT Raw View
Hi,
James.Kanze@dresdner-bank.com wrote:
: In article <0c2c01c07aab$ab163710$0500a8c0@dragonsys.com>,
: "David Abrahams" <abrahams@mediaone.net> wrote:
: > There are none. An input or output iterator is free to report an
: > error any way it sees fit, so long as that doesn't conflict with its
: > other requirements. Some possibilities include:
: > 1. Throwing an exception
: > 2. Setting some global error code
: > 3. Setting an error code in some related object, e.g. a stream.
: Are you sure? (It true, it would pretty much render them unusable.)
Note that Dave is refering to "output iterators" and *not* to
'std::ostream_iterator' specifically! The error handling schemes for
user defined output iterators are up to the implementer of the
iterator. Iterators defined by the standard have specific error
handling schemes as defined by the standard. 'std::ostream_iterator'
will specifically set the error flags on the stream (well, actually it
is not the 'std::ostream_iterator' which sets the flags but the used
'operator<<()') but will not indicate failure during a typical
algorithm like eg. 'std::copy()': That is, the whole input sequence is
traversed which might mean that it is consumed from an
'std::istream_iterator', even if writing to the 'std::ostream_iterator'
fails during writing to it. Unless, of course, the stream is set up to
throw an exception in whic case the algorithm is terminated due to this
exception.
--
<mailto:dietmar_kuehl@yahoo.de> <http://www.dietmar-kuehl.de/~kuehl/>
Phaidros eaSE - Easy Software Engineering: <http://www.phaidros.com/>
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "David Abrahams" <abrahams@mediaone.net>
Date: Sat, 13 Jan 2001 18:35:30 GMT Raw View
----- Original Message -----
From: "cjoy" <cjoy@houston.rr.com>
> In article <0f6d01c07bd9$033ea250$0500a8c0@dragonsys.com>,
> "David Abrahams" <abrahams@mediaone.net> wrote:
> > From: "cjoy" <cjoy@houston.rr.com>
> > > In article <0c2c01c07aab$ab163710$0500a8c0@dragonsys.com>,
> > > "David Abrahams" <abrahams@mediaone.net> wrote:
> > >
> <...snip...>
>
> Thank you for the helpful comments.
> Sorry about the constructor example I should
> have used insert, e.g.,
> vector<int> W;
> W.insert(W.begin(),istream_iterator<int>(cin), istream_iterator<int>());
>
> as this *can* save partial data in the face of exceptions. (Whether it
> *will*
> save partial data seems to be implementation specific as discussed
before).
Yes, but it's the implementation of the operation (not the iterator) that's
in question, right? After all, you are /going/ to cause a program state
change if an InputIterator with side-effects is passed as a parameter. If
the operation gives the strong guarantee that if an exception is thrown
there are no effects*, it will not be able to save any of the data read from
the iterator. In this case, the state change looks to you like "losing
data", but I believe the idea that it's avoidable is a misapprehension due
to the fact that you're dealing with a container. Most standard library
algorithms don't have the luxury of a resizable area in which to store data
that is read from a stream and can't be put back.
*to understand that guarantee correctly, you must realize that side-effects
of user-supplied types (including iterators) are not covered.
> *Sigh*, anyway it sounds like the upshot is that for the library of
database
> i/o iterators I am writing I am going to have to make them not throw
> and use some kind of set-error-handler mechanism to report errors if I
want
> them to be 'data safe' with STL methods.
I think you're drawing the wrong conclusion here. If you /don't/ throw
exceptions, but instead allow the algorithms to run to completion, they will
neccessarily have read or written some invalid data using the iterator. How
will you know which of the data is invalid? How can you legitimately call
that "data safe"?
> This isn't so hard, but I still
> kind of wish for an error standard on i/o iterators .
What kind of an error standard would you like? Can you be specific?
...and when you say i/o iterators, could you be specific about whether
you're addressing the InputIterator and OutputIterator concepts, or the
istream_iterator<> and ostream_iterator<> class templates, or really your
own iterator classes?
[BTW, if it's the latter, you're free to make up your own standard ;-) ]
> Without it, the way I
> raise & report errors
> could be very different from the way the next guy implements iterator
errors
> making it more difficult to write generic code that can work across any
> i/o iterator type & handle errors uniformly.
I think the only way you're going to get careful error handling from code
using iterators is to write it so that operations are aborted /immediately/
upon detection of an error (or you'll end up manipulating invalid data, as
described above). Given that, exceptions are a natural approach to take.
> I understand that one can get
> at the errors in the
> case of stream iterators by going back to the underlying stream, but
having
> an abstract error standard
> at the input/output iterator level would be helpful for folks that want to
> write generic algorithms on i/o iterators.
I think at least one of us isn't thinking very carefully. I don't see how it
would help.
-Dave
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "David Abrahams" <abrahams@mediaone.net>
Date: Tue, 16 Jan 2001 01:20:58 GMT Raw View
----- Original Message -----
From: "cjoy" <cjoy@houston.rr.com>
> O.K. - I've been thinking about the topic of Input and Output iterator
> errors some more & I think I have it a bit clearer as to what I am trying
to
> say. First, some definitions:
>
> InputIterator and OutputIterator - by this I will mean the generic
> InputIterator and OutputIterator concepts in STL.
>
> i/o Iterator - shorthand for "InputIterator or OutputIterator"
>
> "Abraham's Strong Guarantee" or "aCi", by this I mean the ACID
transactional
> guarantee as defined in "Guru of the Week #61",
> http://www.peerdirect.com/resources/gotw061a.html, i.e. the operation is
> a = atomic, if the operation fails the object will be in its initial
state,
> otherwise in its final state
> C = consistent, if op succeeds or fails the system will be in a
'Consistent'
> state with no integrity constraints violated, no memory leaked etc.
> i = isolated, if operation fails, no side effects (may eventually want to
> extend this analysis to data isolation on iterators as is done for
> transactions in a database, but this is beyond the scope of what I want to
> talk about here).
Okay, I never liked the ACID classifications for understanding
exception-safety (too complicated, too little additional analytical
benefit), but I'll try to operate in that world...
> Claim 1: Currently the STL makes aCi guarantees for some of its
operations.
> These aCi guarantees are only with respect to the STL object acted upon
and
> make no promise about state of the iterators that they act upon.
More accurately, no promises are made about atomicity with respect to
side-effects that may occur through user-supplied types. For example, if a
user supplies an iterator to a template function giving the strong
guarantee, and if advancing that iterator causes side-effects, the function
does not promise to undo those side-effects in the event of an exception.
Rationale: how could it be otherwise? You can't roll back an arbitrary
side-effect (e.g. the launching of a rocket).
> My claim
> is that if and only if we require iterators to be aCi over a range
Which you define below, but I will repeat here because I have issues with
this definition.
> Definition: An iterator is aCi over a range if it displays:
> a: atomicity - it must display the four key phases of a transaction.
What does it mean to "display a phase of a transaction"?
> A command to mark the beginning on a transaction on a range, a command to
mark
> the end of a transaction on a range, a command to commit changes on a
range
> and a command to rollback a transaction on a range.
The above is not a sentence. Do you mean that the iterator must support
these additional operations? I think there are more operations here than
strictly neccessary (you could do it with start, commit, and rollback), and
I wouldn't want to see these as part of the iterator.
> C: we say an iterator is consistent if all operations on that iterator are
> consistent over the range
<curmudgeon>Yeah, yeah, yeah. Consistency is a basic underlying assumption
of having a correct program. Let's not complicate things unneccessarily
</curmudgeon>
> i: we say that an iterator is isolated over a range, if, when a set of
> changes on a range are rolled back, those changes are not visible to any
> iterators that are not copies of the given iterator.
That's a poor definition of isolation, because it doesn't cover side-effects
that are visible to things other than iterators.
Let's decomplexify <g> this. You're saying "Only if there is a way to roll
back the side-effects of advancing and dereferencing iterators..."
which I'll rephrase as:
"Only if there is a uniform way to roll back the side-effects of operations
on user-supplied types..."
> , then STL
> operations over that range can be "jointly aCi" in that both the effect on
> the STL object and the effect on the iterator can be made simultaneously
> aCi.
"...can a generic operation with type parameters be made to have no effect
at all when an exception is thrown."
Note that if you can find a way to roll back the side-effects on a
user-defined type, the easy (and non-intrusive) way to strengthen an
operation giving the strong guarantee answer is to wrap it:
strengthened_op(MyInputIterator start, MyInputIterator end);
{
MyTransaction atom(start);
try {
op(start, end); // strong guarantee
}
catch(...) {
atom.rollback();
throw;
}
atom.commit();
}
> Claim 2: Often the "jointly aCi" guarantee over a range is not what you
want
> for an algorithm. Instead, if we assume the each individual operation to
> read or write to our i/o Iterator is aCi, then we may want a "maximal"
> property which says that we will try to execute the maximum possible
number
> of successful atomic transactions against the given iterator. In this
case,
> without 'range atomicity' on the underlying iterator I think the best we
can
> hope for is consistency at the end of the algorithm.
I don't see how 'range atomicity' would help you with this one, anyway.
> Question 1: If the properties given in Claims 1 or 2 are seen to be
useful,
> does it make sense to add methods to the InputIterator and OutputIterator
> standard so that these guarantees can be given in a generic way and/or
> errors can be returned in a standard way?
I'm not convinced there's anything to be gained.
>
> Proof of Claim 1:
> if: if an iterator x, is aCi and an algorithm f, is aCi then we may obtain
> f(x) aCi via the following pseudo-code
>
> try {
> f(x);
> x.commit(); // we assume commit & rollback must always succeed to avoid
> two-phase commit type logic
> }
> catch(.) {
> x.rollback();
> }
>
> only if: if f is not aCi,(and cannot be made to be aCi by executing it on
a
> temporary object) then we cannot rollback any partial changes made by f by
> definition.
Careful, that's not a proof of anything; just another way of making the
claim.
> Similarly for x, e.g.
> *x = data1; x++
> *x=data2; x++;
> *x=data3; x++; // oops, error here, cannot roll operation back to
beginning
> of range
>
> Additional comments on claim 1:
> To make transactional iterators more compatible with existing algorithms,
it
> would likely be more practical to map transactional statements to i/o
> Iterators in the following manner
>
> Define a Transaction object
> Transaction() {// on construction begin a transaction }
> ~Transaction() {// on destruction rollback this transaction}
> begin(), end(), commit() methods
I think this is getting out-of-hand. Even if you do this, you'll eventually
have transactions which are composed of multiple standard-library calls.
It's very similar to the problem of locking objects for multithreading. The
standard library shouldn't be trying to provide transactional semantics for
each separate operation and we especially shouldn't bend the
InputIterator/OutputIterator requirements and the requirements on standard
algorithms to attempt to roll back side-effects on user-supplied types, any
more than each call should be protected by a mutex or additional
requirements should be established on user-supplied types that they support
mutex locking interfaces. Phew!
<snip> Sorry, the rest is very complicated and I'm not convinced it's
heading in a direction I want to pursue. I hope the above convinces you to
take a different approach, because it's highly unlikely that the standard
requirements on i/o iterators will be expanded in the way you suggest; it's
especially unlikely that any such change will happen soon. If you are trying
to get real work done I suggest you pick an approach that helps you work
with the standard library /as written/.
-Dave
P.S. I've been scrubbing my walls and may have inhaled too much bleach. No
flame intended.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "cjoy" <cjoy@houston.rr.com>
Date: Mon, 15 Jan 2001 20:19:57 CST Raw View
In article <0f6d01c07bd9$033ea250$0500a8c0@dragonsys.com>,
"David Abrahams" <abrahams@mediaone.net> wrote:
> From: "cjoy" <cjoy@houston.rr.com>
> > In article <0c2c01c07aab$ab163710$0500a8c0@dragonsys.com>,
> > "David Abrahams" <abrahams@mediaone.net> wrote:
> >
<...snip...>
Thank you for the helpful comments.
Sorry about the constructor example I should
have used insert, e.g.,
vector<int> W;
W.insert(W.begin(),istream_iterator<int>(cin), istream_iterator<int>());
as this *can* save partial data in the face of exceptions. (Whether it
*will*
save partial data seems to be implementation specific as discussed before).
*Sigh*, anyway it sounds like the upshot is that for the library of database
i/o iterators I am writing I am going to have to make them not throw
and use some kind of set-error-handler mechanism to report errors if I want
them to be 'data safe' with STL methods. This isn't so hard, but I still
kind of wish for an error standard on i/o iterators . Without it, the way I
raise & report errors
could be very different from the way the next guy implements iterator errors
making it more difficult to write generic code that can work across any
i/o iterator type & handle errors uniformly. I understand that one can get
at the errors in the
case of stream iterators by going back to the underlying stream, but having
an abstract error standard
at the input/output iterator level would be helpful for folks that want to
write generic algorithms on i/o iterators.
Thanks again.
CJ
[1] What prompted this topic for me is that I am co-authoring an open source
template
library of Input and Output iterators to read/write from general databases
- http://www.geocities.com/corwinjoy/dtl/index.htm
and began to worry if these would interact in 'data safe' ways with the STL
methods in the presence
of errors using exception reporting - (e.g. for bad rows when reading
from a database, errors when writing a row etc.)
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: James Kanze <kanze@gabi-soft.de>
Date: Tue, 16 Jan 2001 02:22:18 GMT Raw View
"David Abrahams" <abrahams@mediaone.net> writes:
|> <James.Kanze@dresdner-bank.com> wrote in message
|> news:93kjkh$6bm$1@nnrp1.deja.com...
|> > In article <0c2c01c07aab$ab163710$0500a8c0@dragonsys.com>,
|> > "David Abrahams" <abrahams@mediaone.net> wrote:
|> > > "cjoy" <cjoy@houston.rr.com> wrote in message
|> > > news:001301c079ed$bb5258c0$6732a218@houston.rr.com...
|> > > > So, I've been reading & re-reading the standard and I can't
|> > > > seem to find any documentation on the error standards for
|> > > > input & output iterators.
|> > > There are none. An input or output iterator is free to report an
|> > > error any way it sees fit, so long as that doesn't conflict with
|> > > its other requirements. Some possibilities include:
|> > > 1. Throwing an exception
|> > > 2. Setting some global error code
|> > > 3. Setting an error code in some related object, e.g. a stream.
|> > Are you sure? (It true, it would pretty much render them
|> > unusable.) The semantics of the iterators is defined in terms of
|> > reading and writing on the underlying streams,
|> You're misreading my post the same way the OP misread the standard.
|> InputIterator (the concept) and istream_iterator<> (the class
|> template) are separate issues.
They're definitely separate issues. Maybe I'm reading 17.4.4.8 wrong;
it says that any function can throw an exception specified by its
exception specification. The ++ operator for istream_iterator<> has no
exception specification (and no additional text saying what exception it
can or cannot throw, under what conditions), so I would interpret this
as meaning that it can throw *any* exception, for any reason it wishes.
But as I say, I'm not quite sure how to interpret this:
- Since there is no exception described in the exception
specification, it cannot, in fact, throw any exception. Except that
I can't believe this. If I activate exceptions on the underlying
stream, it should throw an exception. If the operator>> requires
memory, it should certainly be allowed to throw bad_alloc.
- No exception specification means any exception, according to the
language specification. If istream_iterator<>::operator++ can throw
any exception, just because the implementor feels like, it is pretty
useless; in particular, if I've designed my code so that istream
doesn't throw, and don't expect it to throw, a ++ which converts an
ios::fail to an exception will cause my code to fail. Is the intent
that this be a quality of implementation issue?
- Finally, I guess one could interpret this to mean that the function
throws no exceptions itself, but doesn't catch any either, so
exceptions from operator>> (from istream or from the object being
read) will percolate up. This is the interpretation I would like,
but it takes a lot of imagination to find it in the standard.
|> > so I think that 3 is required. I
|> > don't think that 2 is forbidden; standard functions can set any global
|> > variable they want, as long as its name is in the implementation name
|> > space. According to 17.4.4.8, they can report an error with an
|> > exception. IMHO, this is an oversight; the iterators themselves don't
|> > directly generate any errors, and should not be allowed to arbitrarily
|> > throw.
|> An arbitrary InputIterator may directly generate errors. Are you
|> saying that you'd like some additional guarantees for
|> istream_iterator<>?
Are you saying that e.g. the following implementation is legal:
istream_iterator<T>&
istream_iterator<T>::operator++()
{
throw "I don't like you" ;
}
If, instead of istream_iterator, it were a user defined iterator, I
would argue that this IS legal. Useless, of course. But legal. I
would hope that it isn't legal for istream_iterator. Which means that
istream_iterator does offer additional guarantees.
|> > They *should* pass any exceptions on, e.g. bad_alloc or if the
|> > programmer has initialized the underlying stream to throw. If my
|> > code is counting on checking the status of the stream after
|> > iteration, however, throwing from within the iterator on an error
|> > will cause the error to go unnoticed.
|> Too bad, eh?
Dave, I think I know you well enough to know that if this is your
reaction, you didn't understand what I was trying to say. If, as a
user, I initialize an istream not to throw, and I instantiate
istream_iterator for a type whose operator>> doesn't throw, I would
expect to be able to write my code confident that other than a possible
std::bad_alloc due to buffer allocation, all errors will be reported via
the state bits of the istream. If istream_iterator converts ios::fail
to an exception of type char const*, for example, my code will break.
You don't really mean to say that an implementor has that much freedom,
do you.
|> > (I'm curious to know what the justifying logic of 17.4.4.8 is.
|> > Off hand, I'd say that the only effect is to make all use of the
|> > standard library implementation defined, which I hope wasn't the
|> > intent.)
|> Which paragraph in particular are you curious about? Before I got
|> started with that section, it appeared to be designed to make any
|> use of exceptions with the standard library undefined
|> behavior. Paragraph 1 in particular was there before I came on the
|> scene and I didn't have much hope of changing anything, much less
|> anything that didn't absolutely need changing.
I don't think I have a problem with 17.4.4.8 directly. I have a problem
with it because most of the standard library functions don't have
Throws: clauses, and most don't have exception specifications. And the
obvious interpretation is that in such cases, the function can throw
anything, anytime.
I'd much rather see something to the effect that a library function
cannot *generate* an exception itself, unless it has a Throws clause
which specifies what exception it generates and under what conditions.
Added to a statement that no standard library function "traps"
exceptions; if it catches an exception in order to clean up, it rethrows
it. So that exceptions from user defined instantiation classes are
guaranteed to propagate up to be visible.
I'm even more upset after reading the footnotes, in particular 178.
There is a very strong implication that a simple 'throw "I don't like
you"' is a legal implementation for every function in the standard
library.
What this section seems to be saying is that if you want portable,
robust code, you cannot use the standard library, because there is no
way of knowing what functions may generate what errors, nor how the
errors will be reported.
This has nothing to do with exceptions, as such. I'd be just as upset
if the C standard said that an implementation could report errors not
specified in the standard, and that the return type of the functions
which might report an error was implementation defined.
|> > > > To wit, suppose that I instantiate an output iterator such as
|> > > > ostream_iterator<int> (cout, "\n");
|> > > > // and then try to write to it via
|> > > > *x = 5;
|> > > > // How do I detect an error?
|> > > It depends on the state of cout. See basic_ios<>::exceptions() in
|> > > 27.4.4.3
|> > I much prefer that. But you just said above that it depends on
|> > the implementation.
|> You're right. I read the OP as meaning "how do I detect a stream
|> error?", but I guess one might need to watch for an exception here
|> also.
There are several definite cases to consider with current
implementations:
- The user must check for std::bad_alloc. As has always been the
case, when and if std::istream (or more correctly std::streambuf)
allocates buffers is undefined. In the case of classical
(pre-exception) iostream, it has been very vague as to what happens
if the allocation fails -- I think that most implementations had one
byte buffers in the streambuf, and used them, but it certainly isn't
something I would count on. (Which leads to another question:
should this be required behavior? I don't think so, but I do think
it should be permitted. Which causes a problem with my statement
above, that a standard function doesn't trap an exception.)
- If (and only if) the stream has been programmed to generate
exceptions, the user must be prepared to handle these exceptions.
Of course, a user who doesn't want exceptions will not program the
stream to generate them.
- Finally, if a user-defined operator>> throws, the user has to cope
with it as well. But again, this is under control of the user.
I don't think that a user should have to cope with anything else. In
particular, I don't expect operator>>( istream& , int& ) (nor
istream_iterator::operator++) to throw a "char const*", and if it does,
all of my code is broken. Apparently, 17.4.4.8 breaks all of my code.
And all code that I've ever seen of anyone elses. This is also a
radical incompatibility between the classical iostream and the standard
iostream. (And I've been telling everyone that the only significant
different at the user level was eof and the type of the fmtflags.)
[...]
|> > The failbit will *always* be set at the end of iteration. What you
|> > are looking for is std::failbit && ! std::eof. (I'm not sure how this
|> > maps into the exception generation. Can you configure an IO stream to
|> > throw an exception on failure, except when the failure is due to end
|> > of file?)
|> I don't know; I sure never tried to fix streams ;-)
|> The streams don't seem to be well-designed for error-reporting via
|> exception-handling. For example, if you stream from one file and
|> into another, how can you tell on which file there was a read error?
|> The exception doesn't neccessarily contain that information.
IMHO, error-reporting is a weakness of istream in general. Typically,
you try to break a file up into "records" (often lines), then parse
them. In case of a parse error, however, about all you can do is skip
the record (unless the input is on an interactive device).
This actually translates fairly well to istream_iterators, if you can
easily identify the end of a record (trivial if a record is a line).
Basically, you define a type which represents the record, write an
operator>> for it, and instantiate the istream_iterator on it. Input
stops if you have an error. You check that it isn't end of file, handle
the erroneous record by hand, realigning at the start of the next
record, and create a new iterator to finish. If the handling of the
erroneous record is to ignore it, and '\n' is the record separator, then
all that is needed is istream::ignore.
--
James Kanze mailto:kanze@gabi-soft.de
Conseils en informatique orient e objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelh ttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "cjoy" <cjoy@houston.rr.com>
Date: Tue, 16 Jan 2001 08:04:12 CST Raw View
O.K. - I've been thinking about the topic of Input and Output iterator
errors some more & I think I have it a bit clearer as to what I am trying to
say. First, some definitions:
InputIterator and OutputIterator - by this I will mean the generic
InputIterator and OutputIterator concepts in STL.
i/o Iterator - shorthand for "InputIterator or OutputIterator"
"Abraham's Strong Guarantee" or "aCi", by this I mean the ACID transactional
guarantee as defined in "Guru of the Week #61",
http://www.peerdirect.com/resources/gotw061a.html, i.e. the operation is
a = atomic, if the operation fails the object will be in its initial state,
otherwise in its final state
C = consistent, if op succeeds or fails the system will be in a 'Consistent'
state with no integrity constraints violated, no memory leaked etc.
i = isolated, if operation fails, no side effects (may eventually want to
extend this analysis to data isolation on iterators as is done for
transactions in a database, but this is beyond the scope of what I want to
talk about here).
Claim 1: Currently the STL makes aCi guarantees for some of its operations.
These aCi guarantees are only with respect to the STL object acted upon and
make no promise about state of the iterators that they act upon. My claim
is that if and only if we require iterators to be aCi over a range, then STL
operations over that range can be "jointly aCi" in that both the effect on
the STL object and the effect on the iterator can be made simultaneously
aCi.
Claim 2: Often the "jointly aCi" guarantee over a range is not what you want
for an algorithm. Instead, if we assume the each individual operation to
read or write to our i/o Iterator is aCi, then we may want a "maximal"
property which says that we will try to execute the maximum possible number
of successful atomic transactions against the given iterator. In this case,
without 'range atomicity' on the underlying iterator I think the best we can
hope for is consistency at the end of the algorithm.
Question 1: If the properties given in Claims 1 or 2 are seen to be useful,
does it make sense to add methods to the InputIterator and OutputIterator
standard so that these guarantees can be given in a generic way and/or
errors can be returned in a standard way?
Definition: An iterator is aCi over a range if it displays:
a: atomicity - it must display the four key phases of a transaction. A
command to mark the beginning on a transaction on a range, a command to mark
the end of a transaction on a range, a command to commit changes on a range
and a command to rollback a transaction on a range.
C: we say an iterator is consistent if all operations on that iterator are
consistent over the range
i: we say that an iterator is isolated over a range, if, when a set of
changes on a range are rolled back, those changes are not visible to any
iterators that are not copies of the given iterator.
Proof of Claim 1:
if: if an iterator x, is aCi and an algorithm f, is aCi then we may obtain
f(x) aCi via the following pseudo-code
try {
f(x);
x.commit(); // we assume commit & rollback must always succeed to avoid
two-phase commit type logic
}
catch(.) {
x.rollback();
}
only if: if f is not aCi,(and cannot be made to be aCi by executing it on a
temporary object) then we cannot rollback any partial changes made by f by
definition. Similarly for x, e.g.
*x = data1; x++
*x=data2; x++;
*x=data3; x++; // oops, error here, cannot roll operation back to beginning
of range
Additional comments on claim 1:
To make transactional iterators more compatible with existing algorithms, it
would likely be more practical to map transactional statements to i/o
Iterators in the following manner
Define a Transaction object
Transaction() {// on construction begin a transaction }
~Transaction() {// on destruction rollback this transaction}
begin(), end(), commit() methods
Each i/o Iterator would then hold a counted pointer to a Transaction object,
i.e.
Iterator() {//construct counted pointer to new transaction}
Iterator(Iterator &) {// copy counted pointer to current transaction for
copy construction and assignment}
~Iterator() {// release counted pointer, Transaction gets destroyed if last
reference}
commit() {//execute Transaction.end(); Transaction.commit();
Transaction.begin()}
to see this in action consider the following pseudo-code
void foo() {
Iterator it;
bar(it);
it.commit();
}
If any exceptions happen during the execution of bar() the enclosing
function foo() will automatically destruct the iterator, thereby forcing a
rollback of all outstanding changes. The counted pointer logic ensures that
any operations on the Iterator will automatically be rolled back even if bar
uses temporary copies of the iterator to do its work. (Here we have set the
Iterator to automatically rollback upon destruction for maximum exception
safety. A reasonable alternative might be for it to commit by default and
force try.catch {rollback} everywhere we want to be range atomic.)
Claim 2: There are going to be times when your i/o Iterator either cannot
provide an atomic range guarantee, or you don't want your algorithm to act
in an atomic fashion over a range. Instead, if we assume the each individual
operation to read or write to our i/o Iterator is aCi, then we may want a
"maximal" property which says that the maximum possible number of successful
atomic transactions will be executed against the given iterator.
Definition: We say that an i/o Iterator is element-wise aCi if reading or
writing an individual element is aCi. This means that for an
OutputIterator, called out, *out = data, must be aCi. For an InputIterator,
called in, data=*in, must be aCi.
Definition. We say that an iterator has gone "bad" if it is no longer able
to read from or write to its underlying data source. Concrete examples
would be
(1) An ostream_iterator to a file where the underlying ostream can no longer
write to the file.
(2) A database input iterator, where the connection to the database is lost
and it can no longer read records from the database table it is pointing at.
(3) A directory iterator, such as the one by Dietmar at
http://www.boost.org/libs/dir_it/dir_it.html, where the connection to the
underlying drive is lost and further filenames cannot be retrieved.
Definition: We say that an iterator read/write operation has "failed" if a
particular data element cannot be read from or written to an iterator but
the iterator has not gone "bad". Usually such a read "failure" will be
caused by that particular element not matching a type or business rule that
is required for each individual data object.
Examples:
(1) A database iterator tries to write the following rows to a table whose
only field is a date field: '01-Jan-1980', 'lala', '01-Jan-1981'. Here the
second element would 'fail' because it is not a valid date that can be
written to the table.
(2) An istream_iterator<int> tries to read the following comma delimited
elements from a file: 1, 2, lala, 4. The element 'lala' cannot be converted
to an <int> and hence 'fails'.
Some iterators have a natural notion of atomic elements (rows in a table,
filenames in a directory). For others, if an element 'fails' the bad data
can affect other elements e.g. an istream_iterator tries to read
1, 2, 345, 6
where 4 is a corrupted byte that has replaced the comma delimiter between 3
and 5 leading to the incorrectly read number '345'. This lack of element
atomicity is a property of the iterator being worked with and is independent
of the 'fail' concept since an element can be 'successfully' read but still
hold a bad value.
To get the 'maximal' property we can do the following:
1. Impose a client requirement that the user defined 'data' type cannot
throw on construction or assignment.
2. For InputIterators use the following logic for ++ (and for construction
when reading the first element);
On ++, try to read the next 'data' element. If fail(), push the resulting
'data' element into an error buffer and try to get the next element e.g.
while (iterator != end-of-stream) {
read information into internal 'data' object;
if (not fail() and not bad() )
break;
if (bad()) {
iterator = end-of-stream;
try {error_buffer.push_back(error information string)} catch(..)
{set_buffer_overflow_flag}
}
else {
try {error_buffer.push_back(data + error information error
information string); } catch(..) {set_buffer_overflow_flag}
}
}
(Here we assume the dereference operator *in, simply returns the contents of
the internal buffer -- yes, we could code the read to happen on *in, but
then we may end up having to return an empty object if the data-stream goes
'bad' and we do not want to throw. Here, setting end-of-stream for the
bad() case lets us terminate the read in a no-throw() kind of way.).
3. For OutputIterators use the similar logic for ++. On ++, try to write the
next 'data' element. If fail() or bad(), push the resulting 'data' element
into an error buffer similar to what is shown for InputIterator.
4. Set up the i/o Iterators so that they do not throw on any of their
operations.
Then, if we invoke an algorithm f(InputIterator, OutputIterator) we will
have the property that every element that could be read from the
InputIterator was either fetched completely or logged to an error buffer (up
to error buffer overflow). Similarly, every element that could be written
to the OutputIterator was either written completely or logged to an error
buffer. Here, one could imagine a *standard* function for accessing such an
error buffer so that errors could be dealt with in a generic way.
Still, this is not a very strong guarantee since f(In, Out) could abort for
reasons unrelated to the iterators it is working on. In any case , I think
the best we can hope for is consistency at the end of f(In, Out), which
should be enforced by the item-wise aCi property of our iterator, regardless
of whether we throw or not.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "cjoy" <cjoy@houston.rr.com>
Date: Tue, 16 Jan 2001 08:04:58 CST Raw View
>Dave wrote:
>...
>If you are trying
>to get real work done I suggest you pick an approach that helps you work
>with the standard library /as written/.
>
> -Dave
>
> P.S. I've been scrubbing my walls and may have inhaled too much bleach. No
> flame intended.
>
Grin! There's no getting around what's out there right now! (Actually, most
of what we are doing here would be compatible with the standard algorithms
as it could be made invisible to them - anyway, I believe I agree with you
that it should be the job of the iterator to define rollback semantics & not
for the standard library to worry about). Thanks for the helpful comments -
I think you make some good points here and your comments have been very
helpful to me in thinking through how iterator errors should work. No flame
taken, and I appreciate your taking the time to look through the stuff I
have posted. You've definitely given Michael and I food for thought and I
think we'll have better iterators as a result.
Thanks,
Corwin
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "David Abrahams" <abrahams@mediaone.net>
Date: Tue, 16 Jan 2001 15:44:42 GMT Raw View
"James Kanze" <kanze@gabi-soft.de> wrote in message
news:86vgri2eel.fsf@alex.gabi-soft.de...
> "David Abrahams" <abrahams@mediaone.net> writes:
>
> |> <James.Kanze@dresdner-bank.com> wrote in message
> |> news:93kjkh$6bm$1@nnrp1.deja.com...
> |> > In article <0c2c01c07aab$ab163710$0500a8c0@dragonsys.com>,
> |> > "David Abrahams" <abrahams@mediaone.net> wrote:
>
> |> > > There are none. An input or output iterator is free to report an
> |> > > error any way it sees fit, so long as that doesn't conflict with
> |> > > its other requirements. Some possibilities include:
>
> |> > > 1. Throwing an exception
> |> > > 2. Setting some global error code
> |> > > 3. Setting an error code in some related object, e.g. a stream.
>
> |> > Are you sure? (It true, it would pretty much render them
> |> > unusable.) The semantics of the iterators is defined in terms of
> |> > reading and writing on the underlying streams,
>
> |> You're misreading my post the same way the OP misread the standard.
> |> InputIterator (the concept) and istream_iterator<> (the class
> |> template) are separate issues.
>
> They're definitely separate issues. Maybe I'm reading 17.4.4.8 wrong;
> it says that any function can throw an exception specified by its
> exception specification. The ++ operator for istream_iterator<> has no
> exception specification (and no additional text saying what exception it
> can or cannot throw, under what conditions), so I would interpret this
> as meaning that it can throw *any* exception, for any reason it wishes.
That's correct.
>
> But as I say, I'm not quite sure how to interpret this:
>
> - Since there is no exception described in the exception
> specification, it cannot, in fact, throw any exception. Except that
> I can't believe this. If I activate exceptions on the underlying
> stream, it should throw an exception. If the operator>> requires
> memory, it should certainly be allowed to throw bad_alloc.
Don't try to make it say what you want ;-) Your first interpretation was
correct.
> - No exception specification means any exception, according to the
> language specification. If istream_iterator<>::operator++ can throw
> any exception, just because the implementor feels like, it is pretty
> useless;
No more so than most other operations in the standard library, which also
don't have any exception-specification or Throws: clause ;-)
BTW, I think the above is a poor implementation, but not useless.
> in particular, if I've designed my code so that istream
> doesn't throw, and don't expect it to throw, a ++ which converts an
> ios::fail to an exception will cause my code to fail. Is the intent
> that this be a quality of implementation issue?
I believe that's correct. I didn't invent this morsel of implementor leeway,
and I probably wouldn't have suggested it myself, so don't blame me!
> - Finally, I guess one could interpret this to mean that the function
> throws no exceptions itself, but doesn't catch any either, so
> exceptions from operator>> (from istream or from the object being
> read) will percolate up. This is the interpretation I would like,
> but it takes a lot of imagination to find it in the standard.
That's because it's not what it says.
> |> An arbitrary InputIterator may directly generate errors. Are you
> |> saying that you'd like some additional guarantees for
> |> istream_iterator<>?
>
> Are you saying that e.g. the following implementation is legal:
>
> istream_iterator<T>&
> istream_iterator<T>::operator++()
> {
> throw "I don't like you" ;
> }
I don't think so; this doesn't have the required semantics for ++. The
standard says "every time ++ is used, the iterator reads and stores a value
of T".
> If, instead of istream_iterator, it were a user defined iterator, I
> would argue that this IS legal.
This is borderline. Can an iterator with this implementation fulfill the
input iterator requirements in Table 72?
> Useless, of course. But legal. I
> would hope that it isn't legal for istream_iterator. Which means that
> istream_iterator does offer additional guarantees.
Only that it must try to do what the standard says it must try to do. The
standard doesn't say /how/ it must try to do that, which doesn't rule out
ways that may throw an exception.
> |> > They *should* pass any exceptions on, e.g. bad_alloc or if the
> |> > programmer has initialized the underlying stream to throw. If my
> |> > code is counting on checking the status of the stream after
> |> > iteration, however, throwing from within the iterator on an error
> |> > will cause the error to go unnoticed.
>
> |> Too bad, eh?
>
> Dave, I think I know you well enough to know that if this is your
> reaction, you didn't understand what I was trying to say. If, as a
> user, I initialize an istream not to throw, and I instantiate
> istream_iterator for a type whose operator>> doesn't throw, I would
> expect to be able to write my code confident that other than a possible
> std::bad_alloc due to buffer allocation, all errors will be reported via
> the state bits of the istream.
Why do you think bad_alloc has special status? I think you're making more
assumptions about the implementation of the standard library than you have a
right to. For better or worse, we all do this when we program (heck, I use a
standard lib implementation which I help to maintain), but it's part of the
struggle of writing portable code to be "consciously ignorant" about these
things.
> If istream_iterator converts ios::fail
> to an exception of type char const*, for example, my code will break.
>
> You don't really mean to say that an implementor has that much freedom,
> do you.
I'm afraid so.
> |> > (I'm curious to know what the justifying logic of 17.4.4.8 is.
> |> > Off hand, I'd say that the only effect is to make all use of the
> |> > standard library implementation defined, which I hope wasn't the
> |> > intent.)
>
> |> Which paragraph in particular are you curious about? Before I got
> |> started with that section, it appeared to be designed to make any
> |> use of exceptions with the standard library undefined
> |> behavior. Paragraph 1 in particular was there before I came on the
> |> scene and I didn't have much hope of changing anything, much less
> |> anything that didn't absolutely need changing.
>
> I don't think I have a problem with 17.4.4.8 directly. I have a problem
> with it because most of the standard library functions don't have
> Throws: clauses, and most don't have exception specifications. And the
> obvious interpretation is that in such cases, the function can throw
> anything, anytime.
Yep. That's old news. I know it's come up in conversations you and I have
had about whether to use exceptions or not.
> I'd much rather see something to the effect that a library function
> cannot *generate* an exception itself, unless it has a Throws clause
> which specifies what exception it generates and under what conditions.
I'm not sure whether I'd like that, but I might, if we made sure to get the
neccessary Throws caluse on all functions which may need to allocate
resources. Some implementors closely guard their ability to provide
additional, as-yet-unanticipated features and benefits to users. Often, it
seems to me, this works against the users they're trying to serve. On the
other hand, we shouldn't go too far in the other direction either. I don't
know where your proposal falls along that spectrum.
> Added to a statement that no standard library function "traps"
> exceptions; if it catches an exception in order to clean up, it rethrows
> it.
You want exception-neutrality. That would be nice.
> So that exceptions from user defined instantiation classes are
> guaranteed to propagate up to be visible.
What's an instantiation class?
> I'm even more upset after reading the footnotes, in particular 178.
> There is a very strong implication that a simple 'throw "I don't like
> you"' is a legal implementation for every function in the standard
> library.
I guess it's arguable, for an extremely low-quality library. I remember
having the same feeling several years ago when I started on
exception-safety. When I finally got to the bottom of this, I found that a
legal compiler is allowed to fail to compile all programs, because it's
implementation limits (1.3.6) are exceeded. I think there are lots of places
where the standard relies on QOI to establish a practical lower-limit on
usable implementations :-(.
> What this section seems to be saying is that if you want portable,
> robust code, you cannot use the standard library, because there is no
> way of knowing what functions may generate what errors, nor how the
> errors will be reported.
I wouldn't draw that conclusion.
You know that all errors are reported via exceptions, except as otherwise
specified (e.g. iostreams).
You know that unless otherwise specified, any standard library function may
report an error.
That's enough. I'm not sure it's optimal, but it's certainly enough.
> This has nothing to do with exceptions, as such. I'd be just as upset
> if the C standard said that an implementation could report errors not
> specified in the standard, and that the return type of the functions
> which might report an error was implementation defined.
Are you certain that it doesn't?
> |> You're right. I read the OP as meaning "how do I detect a stream
> |> error?", but I guess one might need to watch for an exception here
> |> also.
>
> There are several definite cases to consider with current
> implementations:
>
> - The user must check for std::bad_alloc. As has always been the
> case, when and if std::istream (or more correctly std::streambuf)
> allocates buffers is undefined. In the case of classical
> (pre-exception) iostream, it has been very vague as to what happens
> if the allocation fails -- I think that most implementations had one
> byte buffers in the streambuf, and used them, but it certainly isn't
> something I would count on. (Which leads to another question:
> should this be required behavior? I don't think so, but I do think
> it should be permitted. Which causes a problem with my statement
> above, that a standard function doesn't trap an exception.)
>
> - If (and only if) the stream has been programmed to generate
> exceptions, the user must be prepared to handle these exceptions.
> Of course, a user who doesn't want exceptions will not program the
> stream to generate them.
>
> - Finally, if a user-defined operator>> throws, the user has to cope
> with it as well. But again, this is under control of the user.
>
> I don't think that a user should have to cope with anything else. In
> particular, I don't expect operator>>( istream& , int& ) (nor
> istream_iterator::operator++) to throw a "char const*", and if it does,
> all of my code is broken. Apparently, 17.4.4.8 breaks all of my code.
Sorry, James. 17.4.4.8 has been that way for many years, long before I came
on the scene. I've been warning people almost since then; certainly since
GotW#4 in April '97. Herb Sutter picked up the "you can't really avoid
exceptions because the standard library throws them" mantra pretty soon
thereafter.
> And all code that I've ever seen of anyone elses.
You haven't been looking at very many other peoples' code then. I have
certainly sent you code which isn't broken by 17.4.4.8. Are you exaggerating
or am I misunderstanding you?
-Dave
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "Anthony Williams" <anthwil@nortelnetworks.com>
Date: Wed, 17 Jan 2001 19:29:21 GMT Raw View
"David Abrahams" <abrahams@mediaone.net> wrote in message
news:15bb01c07fc5$d20a2920$0500a8c0@dragonsys.com...
>
> "James Kanze" <kanze@gabi-soft.de> wrote in message
> news:86vgri2eel.fsf@alex.gabi-soft.de...
> > They're definitely separate issues. Maybe I'm reading 17.4.4.8 wrong;
> > it says that any function can throw an exception specified by its
> > exception specification. The ++ operator for istream_iterator<> has no
> > exception specification (and no additional text saying what exception it
> > can or cannot throw, under what conditions), so I would interpret this
> > as meaning that it can throw *any* exception, for any reason it wishes.
>
> That's correct.
I think it would make sense if the standard library was guaranteed to only
throw exceptions derived from std::exception. That way you wouldn't have to
bother with catching such things as "const char*" unless some of your code
(e.g. operator<< for a UDT) throws it. And most standard library
implementations provide something reasonably meaningful for the return of
std::exception::what(), even if it's just the name of the class that threw
the exception.
Just my $0.02
Anthony
--
Anthony Williams
Software Engineer, Nortel Networks
The opinions expressed in this message are mine and do not represent those
of my employer
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: James.Kanze@dresdner-bank.com
Date: Wed, 17 Jan 2001 22:48:56 GMT Raw View
In article <15bb01c07fc5$d20a2920$0500a8c0@dragonsys.com>,
"David Abrahams" <abrahams@mediaone.net> wrote:
[...]
> > They're definitely separate issues. Maybe I'm reading 17.4.4.8
> > wrong; it says that any function can throw an exception specified
> > by its exception specification. The ++ operator for
> > istream_iterator<> has no exception specification (and no
> > additional text saying what exception it can or cannot throw,
> > under what conditions), so I would interpret this as meaning that
> > it can throw *any* exception, for any reason it wishes.
> That's correct.
[...]
> > in particular, if I've designed my code so that istream
> > doesn't throw, and don't expect it to throw, a ++ which
> > converts an ios::fail to an exception will cause my code to
> > fail. Is the intent that this be a quality of implementation
> > issue?
> I believe that's correct. I didn't invent this morsel of implementor
> leeway, and I probably wouldn't have suggested it myself, so don't
> blame me!
Nobody's blaming you:-). If it weren't for you, the standard would
certainly give a lot less guarantees that it does.
I'm just trying to find out what is and what isn't guaranteed. And
you seem to be the only one who really knows what the standard says in
this regard.
[...]
> > - Finally, I guess one could interpret this to mean that the
> > function throws no exceptions itself, but doesn't catch any
> > either, so exceptions from operator>> (from istream or from
> > the object being read) will percolate up. This is the
> > interpretation I would like, but it takes a lot of imagination
> > to find it in the standard.
> That's because it's not what it says.
A good question might be: why not? Why should an istream_iterator be
allowed to generate new exceptions? Why should it be allowed to, say,
throw a C style string (char const*) if it detects end of file? I can
easily see what this freedom costs us; I can't see what we gain by it.
> > |> An arbitrary InputIterator may directly generate errors. Are
> > |> you saying that you'd like some additional guarantees for
> > |> istream_iterator<>?
> > Are you saying that e.g. the following implementation is legal:
> > istream_iterator<T>&
> > istream_iterator<T>::operator++()
> > {
> > throw "I don't like you" ;
> > }
> I don't think so; this doesn't have the required semantics for
> ++. The standard says "every time ++ is used, the iterator reads and
> stores a value of T".
It obviously has a very original definition of "failure". But if the
standard basically says that an implementation can throw any exception
it pleases, for any reason it pleases, how does this fail to conform.
In your opinion, would it be conform if, after doing whatever it had
to do, it ended in:
if ( ! *in_stream ) throw "Too bad" ;
?
> > If, instead of istream_iterator, it were a user defined iterator,
> > I would argue that this IS legal.
> This is borderline. Can an iterator with this implementation fulfill
> the input iterator requirements in Table 72?
Yes and no. Table 72 doesn't say what the behavior of an iterator is
in case of failure. An obvious case is a trivial iterator, which
iterates over an always empty sequence. In this case, the
precondition for ++ is never met, and so it is guaranteed that the
function will never be called. More generally, however, what happens
if the operator ++ needs more memory, and there isn't any? Nothing in
table 72 says that it cannot throw bad_alloc, but I think we would
both agree that that is the intent, or at least the preferred
behavior.
It's interesting to note that the text of 17.4.4.8 only applies to the
implementation, not to user classes. If the text of 17.4.4.8 has
precedence over table 72, and table 72 is interpreted to forbid
throwing (which I don't think it should be), then we have the case
where the standard library has the right to throw for no reason at
all, but the user code doesn't.
Just curious: given the following code at the start of an operator++:
char* p = malloc( 1000000000 ) ; // That's 10^9, 1 Gigabyte.
if ( p == NULL ) throw "Out of memory" ;
Is this legal in a user defined iterator? In one of the iterators in
the library? (I think the answer is yes to both. Note that on the
machines I run on, this is effectively the equivalent of an iterator
whose operator++ just contains the single throw statement.)
> > Useless, of course. But legal. I would hope that it isn't legal
> > for istream_iterator. Which means that istream_iterator does
> > offer additional guarantees.
> Only that it must try to do what the standard says it must try to
> do. The standard doesn't say /how/ it must try to do that, which
> doesn't rule out ways that may throw an exception.
So it all boils down to quality of implementation. On further
thought, I guess it is acceptable, since usefulness really does boil
down to quality of implementation. When it comes down to it, it's no
more useless than an implementation that generates a five minute empty
loop after each source instruction, and the standard doesn't forbid
that either. (The example is from Steve Clamage, as to why the
standard cannot guarantee usefulness.) I do wish that the intent (if
not the actual requirements) concerning exceptions had been a bit more
clear, however.
> > |> > They *should* pass any exceptions on, e.g. bad_alloc or if
> > |> > the programmer has initialized the underlying stream to
> > |> > throw. If my code is counting on checking the status of the
> > |> > stream after iteration, however, throwing from within the
> > |> > iterator on an error will cause the error to go unnoticed.
> > |> Too bad, eh?
> > Dave, I think I know you well enough to know that if this is your
> > reaction, you didn't understand what I was trying to say. If, as
> > a user, I initialize an istream not to throw, and I instantiate
> > istream_iterator for a type whose operator>> doesn't throw, I
> > would expect to be able to write my code confident that other than
> > a possible std::bad_alloc due to buffer allocation, all errors
> > will be reported via the state bits of the istream.
> Why do you think bad_alloc has special status? I think you're making
> more assumptions about the implementation of the standard library
> than you have a right to. For better or worse, we all do this when
> we program (heck, I use a standard lib implementation which I help
> to maintain), but it's part of the struggle of writing portable code
> to be "consciously ignorant" about these things.
Just how far can you afford to be ignorant? At some point, you have
to be able to distinguish between normal conditions (an input failed
because of end of stream), and abnormal, perhaps fatal errors (format
error in the input stream, insufficient memory, etc.). Any decent
implementation will provide for this, but unless they all provide for
it in the same way, it is useless for portable code.
Some things are probably (certainly?) safe, regardless of the
liberties given by the standard. If the reason for failure is
insufficient memory, I expect bad_alloc, or something derived from
bad_alloc. Regardless of where the error occurs in the library. If
operator>> fails, I expect istream_iterator 1) to pass any exceptions
on, unchanged, and 2) if no exceptions are thrown, to detect the error
in istream (failbit, etc.), to become effectively a "one past the end"
iterator, and to do *nothing* else.
Is it reasonable to expect all of this, as a quality of implementation
issue?
> > If istream_iterator converts ios::fail to an exception of type
> > char const*, for example, my code will break.
> > You don't really mean to say that an implementor has that much
> > freedom, do you.
> I'm afraid so.
So how do you write portable code? Count on all implementations just
happening to do the right thing?
> > |> > (I'm curious to know what the justifying logic of 17.4.4.8
> > |> > is. Off hand, I'd say that the only effect is to make all
> > |> > use of the standard library implementation defined, which I
> > |> > hope wasn't the intent.)
> > |> Which paragraph in particular are you curious about? Before I
> > |> got started with that section, it appeared to be designed to
> > |> make any use of exceptions with the standard library undefined
> > |> behavior. Paragraph 1 in particular was there before I came on
> > |> the scene and I didn't have much hope of changing anything,
> > |> much less anything that didn't absolutely need changing.
> > I don't think I have a problem with 17.4.4.8 directly. I have a
> > problem with it because most of the standard library functions
> > don't have Throws: clauses, and most don't have exception
> > specifications. And the obvious interpretation is that in such
> > cases, the function can throw anything, anytime.
> Yep. That's old news. I know it's come up in conversations you and I
> have had about whether to use exceptions or not.
But most of the time, to date, the issue has really been whether it
throws or not. We were concerned with exception safety in the
intermediate code. We (or at least I) assumed that somewhere up
above, there was code which knew which exceptions could be thrown
(globally), and knew what to do when they were thrown. In this case,
we have the problem that no code, anywhere in the system, knows what
can occur. About all I can do is catch(...) and abort.
> > I'd much rather see something to the effect that a library
> > function cannot *generate* an exception itself, unless it has a
> > Throws clause which specifies what exception it generates and
> > under what conditions.
> I'm not sure whether I'd like that, but I might, if we made sure to
> get the neccessary Throws caluse on all functions which may need to
> allocate resources.
I'd accept a global statement to the effect that any standard function
may allocate memory (and thus throw bad_alloc) unless otherwise
specified. Provided, of course, that operator delete, malloc and free
were otherwise specified. And ideally, a few other very basic
functions, mostly inherited from C, like memcpy.
For the rest, I do think a guarantee that e.g. vector<> won't catch an
exception from my constructor, and rethrow a char const*, certainly
wouldn't hurt.
I suspect that, as always, getting the proper wording would be less
than trivial.
> Some implementors closely guard their ability to provide additional,
> as-yet-unanticipated features and benefits to users. Often, it seems
> to me, this works against the users they're trying to serve. On the
> other hand, we shouldn't go too far in the other direction either. I
> don't know where your proposal falls along that spectrum.
> > Added to a statement that no standard library function "traps"
> > exceptions; if it catches an exception in order to clean up, it
> > rethrows it.
> You want exception-neutrality. That would be nice.
I do want it. I want a lot of other things to, but this one seems
fairly important if you want to write robust code.
> > So that exceptions from user defined instantiation classes are
> > guaranteed to propagate up to be visible.
> What's an instantiation class?
The class over which a template is instantiated. The T in vector<T>,
for example. (I think it's a word I just made up.)
> > I'm even more upset after reading the footnotes, in particular
> > 178. There is a very strong implication that a simple 'throw "I
> > don't like you"' is a legal implementation for every function in
> > the standard library.
> I guess it's arguable, for an extremely low-quality library. I
> remember having the same feeling several years ago when I started on
> exception-safety. When I finally got to the bottom of this, I found
> that a legal compiler is allowed to fail to compile all programs,
> because it's implementation limits (1.3.6) are exceeded. I think
> there are lots of places where the standard relies on QOI to
> establish a practical lower-limit on usable implementations :-(.
There are. There are cases where there is no real alternative --
"usable" implies a minimum of performance, for example. In the case
of performance, however, we DO have an effective guarantee that no
implementation is going to go out of their way to make it a problem.
Because it is obvious that really bad performance makes an
implementation unusable, and it also affects pretty much every
program, immediately, so it will be immediately recognized if a
compiler does it, and the market will speak.
In some ways, I guess the practical limit concerning what can be left
to quality of implementation is "could Microsoft get away with it".
Even Microsoft couldn't get away with a compiler that failed to
compile any program, or inserted a five minute empty loop after each
source line. I don't think they could get away with an
istream_iterator::operator++ that simply threw a char const* in every
case, either.
I'm less certain about one that remapped certain istream errors in the
istream_iterator. I think that they probably could, but it is hard to
say. IBM isn't just a nobody, but they had to back off with their
malloc that lied. The problem may just be that today, not enough
users are aware of the problem for the implementors to feel any
presure.
> > What this section seems to be saying is that if you want portable,
> > robust code, you cannot use the standard library, because there is
> > no way of knowing what functions may generate what errors, nor how
> > the errors will be reported.
> I wouldn't draw that conclusion. You know that all errors are
> reported via exceptions, except as otherwise specified
> (e.g. iostreams). You know that unless otherwise specified, any
> standard library function may report an error. That's enough. I'm
> not sure it's optimal, but it's certainly enough.
If I know that the errors are all "fatal", and not informative.
> > This has nothing to do with exceptions, as such. I'd be just as
> > upset if the C standard said that an implementation could report
> > errors not specified in the standard, and that the return type of
> > the functions which might report an error was implementation
> > defined.
> Are you certain that it doesn't?
Yes. The return type is fully specified for every function in the
library. The only exception I can think of is resource limits, and
even there, the C standard generally says what the function should do.
Even if one accepts that the general clause concerning resource limits
has precedence over the specific clauses concerning error reporting,
the intent is clear enough that quality of implementation can do the
rest.
Perhaps the problem here is that even the intent is not clear enough.
> > |> You're right. I read the OP as meaning "how do I detect a
> > |> stream error?", but I guess one might need to watch for an
> > |> exception here also.
> > There are several definite cases to consider with current
> > implementations:
> > - The user must check for std::bad_alloc. As has always been
> > the case, when and if std::istream (or more correctly
> > std::streambuf) allocates buffers is undefined. In the case
> > of classical (pre-exception) iostream, it has been very vague
> > as to what happens if the allocation fails -- I think that
> > most implementations had one byte buffers in the streambuf,
> > and used them, but it certainly isn't something I would count
> > on. (Which leads to another question: should this be required
> > behavior? I don't think so, but I do think it should be
> > permitted. Which causes a problem with my statement above,
> > that a standard function doesn't trap an exception.)
> > - If (and only if) the stream has been programmed to generate
> > exceptions, the user must be prepared to handle these
> > exceptions. Of course, a user who doesn't want exceptions
> > will not program the stream to generate them.
> > - Finally, if a user-defined operator>> throws, the user has to
> > cope with it as well. But again, this is under control of the
> > user.
> > I don't think that a user should have to cope with anything
> > else. In particular, I don't expect operator>>( istream& , int& )
> > (nor istream_iterator::operator++) to throw a "char const*", and
> > if it does, all of my code is broken. Apparently, 17.4.4.8 breaks
> > all of my code.
> Sorry, James. 17.4.4.8 has been that way for many years, long before
> I came on the scene. I've been warning people almost since then;
> certainly since GotW#4 in April '97. Herb Sutter picked up the "you
> can't really avoid exceptions because the standard library throws
> them" mantra pretty soon thereafter.
I missed this. I always thought that he meant things like bad_alloc
-- if you use vector<>, for example, you must be prepared for at least
a bad_alloc exception. It never occured to me that vector might
decide to trap this exception, and throw a double in its place. And
I'd never read this particular section before.
> > And all code that I've ever seen of anyone elses.
> You haven't been looking at very many other peoples' code then. I
> have certainly sent you code which isn't broken by 17.4.4.8. Are you
> exaggerating or am I misunderstanding you?
Strictly speaking, most of the code I've seen on the net simply
ignores all possibilities of errors. So 17.4.4.8 doesn't break it any
more than it was already broken. But most robust code I've seen does
try and do something when errors are detected, what it does depends on
the type of error, and 17.4.4.8, taken literally, breaks it.
Anyway, your comments have been a great help in my understanding. I'd
like to thank you for taking the time to address my concerns.
--
James Kanze mailto:kanze@gabi-soft.de
Conseils en informatique orient e objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelh ttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
Sent via Deja.com
http://www.deja.com/
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "cjoy" <cjoy@houston.rr.com>
Date: Tue, 9 Jan 2001 23:15:21 GMT Raw View
So, I've been reading & re-reading the standard and I can't seem to find any
documentation on the error standards for input & output iterators.
To wit, suppose that I instantiate an output iterator such as
ostream_iterator<int> (cout, "\n");
// and then try to write to it via
*x = 5;
// How do I detect an error?
There are at least two distinct cases of interest here, which I will name
after similar flags in the iostream class.
1. Fail. The writing of the given value failed but the output iterator is
still in a 'good' state and further values may be written to it.
2. Bad. The output iterator has gone bad and no further values may be
written.
Even worse, consider what happens when we try to invoke an STL copy
algorithm
to try to read elements from an input iterator into a containter e.g.
vector<int> V;
copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(V));
template <class _InputIter, class _OutputIter, class _Distance>
inline _OutputIter copy(_InputIter __first, _InputIter __last,
_OutputIter __result)
{
for ( ; __first != __last; ++__result, ++__first)
*__result = *__first;
return __result;
}
The problem is, what happens if there is an error while we are trying to
copy from our input iterator?
Again we have our two cases:
1. Fail. Somewhere along the way we may have an element from cin that
cannot be mapped into an int.
If we throw, copy will abort and we lose the rest of the input stream. (This
could frustrate the poor slob at the
keyboard that just finished typing in a long list of numbers since the rest
of the list may get lost). If we try to do some tricky logic to skip
the bad element - the end user of copy will have no idea that the full
stream was not read in and/or which elements
were bad.
2. If the stream goes bad, we can abort the read by setting the iterator =
end-of-stream, again - how to report back
an error?
One solution to this seems to be that one might allow I/O iterators to throw
exceptions but this goes against the
exception safety guarantees of the standard library which only gurantee
exception safety if iterators cannot throw
on dereference, assigment, comparison or increment. So, if one of these
iterators tries to throw to report an error
the STL algorithms may give undefined results.
So - the question - is there a section in the standard for Input and Output
iterator errors?
If not, it seems to me that this is a serious gap that needs to be
addressed.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "David Abrahams" <abrahams@mediaone.net>
Date: Wed, 10 Jan 2001 18:26:02 GMT Raw View
"cjoy" <cjoy@houston.rr.com> wrote in message
news:001301c079ed$bb5258c0$6732a218@houston.rr.com...
> So, I've been reading & re-reading the standard and I can't seem to find
any
> documentation on the error standards for input & output iterators.
There are none. An input or output iterator is free to report an error any
way it sees fit, so long as that doesn't conflict with its other
requirements. Some possibilities include:
1. Throwing an exception
2. Setting some global error code
3. Setting an error code in some related object, e.g. a stream.
> To wit, suppose that I instantiate an output iterator such as
> ostream_iterator<int> (cout, "\n");
> // and then try to write to it via
> *x = 5;
> // How do I detect an error?
It depends on the state of cout. See basic_ios<>::exceptions() in 27.4.4.3
> There are at least two distinct cases of interest here, which I will name
> after similar flags in the iostream class.
> 1. Fail. The writing of the given value failed but the output iterator is
> still in a 'good' state and further values may be written to it.
> 2. Bad. The output iterator has gone bad and no further values may be
> written.
The output iterator is never in a bad state. I can't say the same for the
stream which it writes to (cout), though.
\> Even worse, consider what happens when we try to invoke an STL copy
> algorithm
> to try to read elements from an input iterator into a containter e.g.
>
> vector<int> V;
> copy(istream_iterator<int>(cin), istream_iterator<int>(),
back_inserter(V));
>
> template <class _InputIter, class _OutputIter, class _Distance>
> inline _OutputIter copy(_InputIter __first, _InputIter __last,
> _OutputIter __result)
> {
> for ( ; __first != __last; ++__result, ++__first)
> *__result = *__first;
> return __result;
> }
>
>
> The problem is, what happens if there is an error while we are trying to
> copy from our input iterator?
> Again we have our two cases:
> 1. Fail. Somewhere along the way we may have an element from cin that
> cannot be mapped into an int.
> If we throw, copy will abort and we lose the rest of the input stream.
Why do you think that? You are still free to read from cin.
In general, though, it is true that you may have lost some part of the input
stream. For example, if the input stream contained the characters: -abc you
will probably lose your chance to read the '-' character.
> (This
> could frustrate the poor slob at the
> keyboard that just finished typing in a long list of numbers since the
rest
> of the list may get lost). If we try to do some tricky logic to skip
> the bad element - the end user of copy will have no idea that the full
> stream was not read in and/or which elements
> were bad.
Right.
> 2. If the stream goes bad, we can abort the read by setting the iterator =
> end-of-stream, again - how to report back
> an error?
If cin.exceptions() & std::failbit != 0, you will get an exception.
Otherwise, you can look at cin.fail() to find out if there has been an
error.
> One solution to this seems to be that one might allow I/O iterators to
throw
> exceptions but this goes against the
> exception safety guarantees of the standard library which only gurantee
> exception safety if iterators cannot throw
> on dereference, assigment, comparison or increment.
There is no such requirement in the standard (just to curtail argument: I
should know, since I authored the exception-safety guarantees and
requirements). One popular book makes a misstatement to that effect, but
it's not so. In fact, i/ostream_iterators can and do throw exceptions if the
underlying stream state is appropriately set.
> So, if one of these
> iterators tries to throw to report an error
> the STL algorithms may give undefined results.
As above, it ain't so.
> So - the question - is there a section in the standard for Input and
Output
> iterator errors?
Not specifically. Remember, input iterators and output iterators don't have
anything to do with i/o. They're just a set of abstract requirements, of
which two prime examples are ostream_iterator and istream_iterator.
> If not, it seems to me that this is a serious gap that needs to be
> addressed.
Still?
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "cjoy" <cjoy@houston.rr.com>
Date: Thu, 11 Jan 2001 17:50:28 GMT Raw View
In article <0c2c01c07aab$ab163710$0500a8c0@dragonsys.com>,
"David Abrahams" <abrahams@mediaone.net> wrote:
<...snip...>
An input or output iterator is free to report an error any way it sees fit,
so long as that doesn't conflict with its other requirements. Some
possibilities include:
1. Throwing an exception
2. Setting some global error code
3. Setting an error code in some related object, e.g. a stream.
<...snip>
> Corwin Joy wrote:
>
> One solution to this seems to be that one might allow I/O iterators to
throw
> exceptions but this goes against the
> exception safety guarantees of the standard library which only guarantee
> exception safety if iterators cannot throw
> on dereference, assignment, comparison or increment.
There is no such requirement in the standard (just to curtail argument: I
should know, since I authored the exception-safety guarantees and
requirements). One popular book makes a misstatement to that effect, but
it's not so. In fact, i/ostream_iterators can and do throw exceptions if the
underlying stream state is appropriately set.
> So, if one of these
> iterators tries to throw to report an error
> the STL algorithms may give undefined results.
As above, it ain't so.
-----------------------------------------------------------// end quote
Thank you for the kind response to my e-mail, I would like to suggest,
however, that this leaves one major question unanswered.
If you look at e.g. http://www.stlport.org/doc/exception_safety.html which
defines the exception safety guarantees for STLport & was written by Dave
Abrahams you will see that all STL operations must meet some basic
guarantees in the face of exceptions which include the following:
- Algorithms which construct objects (e.g. uninitialized_fill) either
complete successfully or destroy any objects they have constructed at the
time of the exception.
- Algorithms which operate on ranges of objects leave only fully-constructed
objects in those ranges if they terminate due to an exception.
Given this, consider the following code
vector<int> W(istream_iterator<int>(cin), istream_iterator<int>());
With the above exception guarantees one could imagine the range constructor
being written along two main possible lines:
// Atomic Items - each item to be added to the container will either fully
construct or not at all
void range_construct(_InputIterator __first, _InputIterator __last)
{
iterator __pos = begin();
// if we throw in this loop, any items read in will be 'kept' by the
container
for ( ; __first != __last; ++__first) {
__pos = insert(__pos, *__first); // assume insert here is atomic, i.e.
it either completely succeeds or fails in the face of exceptions
++__pos;
}
}
// Atomic Container - the container will either fully construct or not at
all
void range_construct(iterator __pos, _InputIterator __first, _InputIterator
__last)
{
Container temp();
iterator __pos = temp.begin();
// if our iterator throws in the loop below -- all is lost!
for ( ; __first != __last; ++__first) {
__pos = temp.insert(__pos, *__first); // insert here not assumed
atomic- operates on temp object
++__pos;
}
swap(temp); // no-throw swap to finish construction
}
>From what I could tell by reading the exception guarantees, there didn't
seem to be any specification as to which of these versions an STL library
might use.
The problem is, if your input iterator can throw you may end up really
caring about what happens here.
Suppose you are using a 'read-once' kind of input iterator such as an
istream_iterator to read from the keyboard, an input iterator to
hold transient object messages etc. If your STL library implements 'Atomic
Container' a throw by your iterator will cause all items that were read in
to be destructed, thereby permanently losing any data that was read from
your iterator. What is more, even if you are not using a 'read-once' kind
of iterator it may be hard for the program to 'go - back' & re-read the
elements that were lost by the 'Atomic Container', since it is hard for it
to 'know' how many items were read by the container that self-destructed.
Therefore, unless your library guarantees 'Atomic Items' - it seems to me to
be non-exception safe in a broad sense, since any exception by your iterator
may cause key data read from that iterator to be lost.
The good news is, library implementations such as STLport seem to use the
'Atomic Items' paradigm in their range methods, and hence will not lose data
in the face of exceptions. The bad news is, this doesn't seem to be spelled
out as part of the exception safety standard.
So - unless this is already part of the standard that I have overlooked -
should it be? (My vote would be yes.)
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "David Abrahams" <abrahams@mediaone.net>
Date: Thu, 11 Jan 2001 17:50:31 GMT Raw View
From: "cjoy" <cjoy@houston.rr.com>
> In article <0c2c01c07aab$ab163710$0500a8c0@dragonsys.com>,
> "David Abrahams" <abrahams@mediaone.net> wrote:
>
> Thank you for the kind response to my e-mail, I would like to suggest,
> however, that this leaves one major question unanswered.
> If you look at e.g. http://www.stlport.org/doc/exception_safety.html which
> defines the exception safety guarantees for STLport & was written by Dave
> Abrahams
That's me. Those guarantees apply to the STLport, but not neccessarily to
the standard, FWIW.
> you will see that all STL operations must meet some basic
> guarantees in the face of exceptions which include the following:
> - Algorithms which construct objects (e.g. uninitialized_fill) either
> complete successfully or destroy any objects they have constructed at the
> time of the exception.
> - Algorithms which operate on ranges of objects leave only
fully-constructed
> objects in those ranges if they terminate due to an exception.
>
> Given this, consider the following code
>
> vector<int> W(istream_iterator<int>(cin), istream_iterator<int>());
Note: the above is not an algorithm in the sense described above. The word
"algorithm" above refers to the functions in the algorithms section of the
standard library.
> With the above exception guarantees one could imagine the range
constructor
> being written along two main possible lines:
<snip code>
> From what I could tell by reading the exception guarantees, there didn't
> seem to be any specification as to which of these versions an STL library
> might use.
Right.
> The problem is, if your input iterator can throw you may end up really
> caring about what happens here.
Another problem is, you can lose information from an istream_iterator
whenever there is an error. One example occurs where you try to read an int
from a stream containing "-z". What's the difference between a little lost
information and a lot?
> Suppose you are using a 'read-once' kind of input iterator such as an
> istream_iterator to read from the keyboard, an input iterator to
> hold transient object messages etc. If your STL library implements
'Atomic
> Container' a throw by your iterator will cause all items that were read in
> to be destructed, thereby permanently losing any data that was read from
> your iterator. What is more, even if you are not using a 'read-once' kind
> of iterator it may be hard for the program to 'go - back' & re-read the
> elements that were lost by the 'Atomic Container', since it is hard for it
> to 'know' how many items were read by the container that self-destructed.
> Therefore, unless your library guarantees 'Atomic Items' - it seems to me
to
> be non-exception safe in a broad sense, since any exception by your
iterator
> may cause key data read from that iterator to be lost.
>
> The good news is, library implementations such as STLport seem to use the
> 'Atomic Items' paradigm in their range methods, and hence will not lose
data
> in the face of exceptions.
Sorry; either implementation will produce the same result, since an
exception thrown from within the constructor causes destruction and release
of all the nascent container's resources.
> The bad news is, this doesn't seem to be spelled out as part of the
exception safety standard.
You are correct.
> So - unless this is already part of the standard that I have overlooked -
> should it be? (My vote would be yes.)
No; I don't think it wouldn't do you any good.
-Dave
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: James.Kanze@dresdner-bank.com
Date: Fri, 12 Jan 2001 04:54:45 GMT Raw View
In article <0c2c01c07aab$ab163710$0500a8c0@dragonsys.com>,
"David Abrahams" <abrahams@mediaone.net> wrote:
> "cjoy" <cjoy@houston.rr.com> wrote in message
> news:001301c079ed$bb5258c0$6732a218@houston.rr.com...
> > So, I've been reading & re-reading the standard and I can't seem
> > to find any documentation on the error standards for input &
> > output iterators.
> There are none. An input or output iterator is free to report an
> error any way it sees fit, so long as that doesn't conflict with its
> other requirements. Some possibilities include:
> 1. Throwing an exception
> 2. Setting some global error code
> 3. Setting an error code in some related object, e.g. a stream.
Are you sure? (It true, it would pretty much render them unusable.)
The semantics of the iterators is defined in terms of reading and
writing on the underlying streams, so I think that 3 is required. I
don't think that 2 is forbidden; standard functions can set any global
variable they want, as long as its name is in the implementation name
space. According to 17.4.4.8, they can report an error with an
exception. IMHO, this is an oversight; the iterators themselves don't
directly generate any errors, and should not be allowed to arbitrarily
throw.
They *should* pass any exceptions on, e.g. bad_alloc or if the
programmer has initialized the underlying stream to throw. If my code
is counting on checking the status of the stream after iteration,
however, throwing from within the iterator on an error will cause the
error to go unnoticed.
(I'm curious to know what the justifying logic of 17.4.4.8 is. Off
hand, I'd say that the only effect is to make all use of the standard
library implementation defined, which I hope wasn't the intent.)
> > To wit, suppose that I instantiate an output iterator such as
> > ostream_iterator<int> (cout, "\n");
> > // and then try to write to it via
> > *x = 5;
> > // How do I detect an error?
> It depends on the state of cout. See basic_ios<>::exceptions() in
> 27.4.4.3
I much prefer that. But you just said above that it depends on the
implementation.
> > There are at least two distinct cases of interest here, which I
> > will name after similar flags in the iostream class.
> > 1. Fail. The writing of the given value failed but the output
iterator is
> > still in a 'good' state and further values may be written to it.
> > 2. Bad. The output iterator has gone bad and no further values may
be
> > written.
> The output iterator is never in a bad state. I can't say the same
> for the stream which it writes to (cout), though.
> > Even worse, consider what happens when we try to invoke an STL
> > copy algorithm to try to read elements from an input iterator into
> > a containter e.g.
> > vector<int> V;
> > copy(istream_iterator<int>(cin), istream_iterator<int>(),
back_inserter(V));
> > template <class _InputIter, class _OutputIter, class _Distance>
> > inline _OutputIter copy(_InputIter __first, _InputIter __last,
> > _OutputIter __result)
> > {
> > for ( ; __first != __last; ++__result, ++__first)
> > *__result = *__first;
> > return __result;
> > }
> > The problem is, what happens if there is an error while we are
> > trying to copy from our input iterator?
> > Again we have our two cases:
> > 1. Fail. Somewhere along the way we may have an element from cin
that
> > cannot be mapped into an int.
> > If we throw, copy will abort and we lose the rest of the input
stream.
> Why do you think that? You are still free to read from cin.
In fact, I think the risk is the reverse. If we throw, I may lose the
data already read.
This is only a problem, however, if I don't know whether we throw or
not. If I control the stream, I can determine whether to check it
after, or to use exeptions. In either case, I know what to
catch/verify, and it is up to me to take the necessary steps.
Note that typically, in such cases, I cannot know how much has been
copied. The obvious solution is to copy to a back_inserter, into a
new container (if such information is important).
[...]
> > 2. If the stream goes bad, we can abort the read by setting the
iterator =
> > end-of-stream, again - how to report back
> > an error?
> If cin.exceptions() & std::failbit != 0, you will get an exception.
> Otherwise, you can look at cin.fail() to find out if there has been
> an error.
The failbit will *always* be set at the end of iteration. What you
are looking for is std::failbit && ! std::eof. (I'm not sure how this
maps into the exception generation. Can you configure an IO stream to
throw an exception on failure, except when the failure is due to end
of file?)
--
James Kanze mailto:kanze@gabi-soft.de
Conseils en informatique orient e objet/
Beratung in objektorientierter Datenverarbeitung
Ziegelh ttenweg 17a, 60598 Frankfurt, Germany Tel. +49(069)63198627
Sent via Deja.com
http://www.deja.com/
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]
Author: "David Abrahams" <abrahams@mediaone.net>
Date: Fri, 12 Jan 2001 10:44:42 CST Raw View
<James.Kanze@dresdner-bank.com> wrote in message
news:93kjkh$6bm$1@nnrp1.deja.com...
> In article <0c2c01c07aab$ab163710$0500a8c0@dragonsys.com>,
> "David Abrahams" <abrahams@mediaone.net> wrote:
>
> > "cjoy" <cjoy@houston.rr.com> wrote in message
> > news:001301c079ed$bb5258c0$6732a218@houston.rr.com...
>
> > > So, I've been reading & re-reading the standard and I can't seem
> > > to find any documentation on the error standards for input &
> > > output iterators.
>
> > There are none. An input or output iterator is free to report an
> > error any way it sees fit, so long as that doesn't conflict with its
> > other requirements. Some possibilities include:
>
> > 1. Throwing an exception
> > 2. Setting some global error code
> > 3. Setting an error code in some related object, e.g. a stream.
>
> Are you sure? (It true, it would pretty much render them unusable.)
> The semantics of the iterators is defined in terms of reading and
> writing on the underlying streams,
You're misreading my post the same way the OP misread the standard.
InputIterator (the concept) and istream_iterator<> (the class template) are
separate issues.
> so I think that 3 is required. I
> don't think that 2 is forbidden; standard functions can set any global
> variable they want, as long as its name is in the implementation name
> space. According to 17.4.4.8, they can report an error with an
> exception. IMHO, this is an oversight; the iterators themselves don't
> directly generate any errors, and should not be allowed to arbitrarily
> throw.
An arbitrary InputIterator may directly generate errors.
Are you saying that you'd like some additional guarantees for
istream_iterator<>?
> They *should* pass any exceptions on, e.g. bad_alloc or if the
> programmer has initialized the underlying stream to throw. If my code
> is counting on checking the status of the stream after iteration,
> however, throwing from within the iterator on an error will cause the
> error to go unnoticed.
Too bad, eh?
> (I'm curious to know what the justifying logic of 17.4.4.8 is. Off
> hand, I'd say that the only effect is to make all use of the standard
> library implementation defined, which I hope wasn't the intent.)
Which paragraph in particular are you curious about? Before I got started
with that section, it appeared to be designed to make any use of exceptions
with the standard library undefined behavior. Paragraph 1 in particular was
there before I came on the scene and I didn't have much hope of changing
anything, much less anything that didn't absolutely need changing.
> > > To wit, suppose that I instantiate an output iterator such as
> > > ostream_iterator<int> (cout, "\n");
> > > // and then try to write to it via
> > > *x = 5;
> > > // How do I detect an error?
>
> > It depends on the state of cout. See basic_ios<>::exceptions() in
> > 27.4.4.3
>
> I much prefer that. But you just said above that it depends on the
> implementation.
You're right. I read the OP as meaning "how do I detect a stream error?",
but I guess one might need to watch for an exception here also.
> > Why do you think that? You are still free to read from cin.
>
> In fact, I think the risk is the reverse. If we throw, I may lose the
> data already read.
Yep.
> This is only a problem, however, if I don't know whether we throw or
> not. If I control the stream, I can determine whether to check it
> after, or to use exeptions. In either case, I know what to
> catch/verify, and it is up to me to take the necessary steps.
>
> Note that typically, in such cases, I cannot know how much has been
> copied. The obvious solution is to copy to a back_inserter, into a
> new container (if such information is important).
>
> [...]
> > > 2. If the stream goes bad, we can abort the read by setting the
> iterator =
> > > end-of-stream, again - how to report back
> > > an error?
>
> > If cin.exceptions() & std::failbit != 0, you will get an exception.
> > Otherwise, you can look at cin.fail() to find out if there has been
> > an error.
>
> The failbit will *always* be set at the end of iteration. What you
> are looking for is std::failbit && ! std::eof. (I'm not sure how this
> maps into the exception generation. Can you configure an IO stream to
> throw an exception on failure, except when the failure is due to end
> of file?)
I don't know; I sure never tried to fix streams ;-)
The streams don't seem to be well-designed for error-reporting via
exception-handling. For example, if you stream from one file and into
another, how can you tell on which file there was a read error? The
exception doesn't neccessarily contain that information.
-Dave
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
[ Note that the FAQ URL has changed! Please update your bookmarks. ]