Boost logo

Boost :

From: George A. Heintzelman (georgeh_at_[hidden])
Date: 2001-07-30 17:24:22


> At least that's why my code (when using C++ on a platform that
> supports exceptions at least) uses exceptions (and in fact frequently
> uses a very thin wrapper layer that calls the normal Unix network
> stuff, and then throws an exception if it gets an error return).

I agree with this.

> NOTE: I'm not counting short reads/writes or EOF as an error.

I don't think it would count as an error, but I was thinking at the
iostream level about using the exception handling mechanism to unwind
an incomplete read. As Stroustrup says, not all exceptions need really
be 'errors'.

Ie, suppose:

struct X {
  int mem1, mem2, mem3, mem4;
};
operator>>(istream &is, X &x) {
  X temp;
  if (is.fail()) return is;
  is >> temp.mem1 >> temp.mem2 >> temp.mem3 >> temp.mem4;
  if (!is.fail())
    swap(temp,x);
  return is;
}

nonblocking_iostream istrm( ... );
// ... in some loop ...
X obj;
istrm >> obj;

Now, suppose when the iostream tried to get stuff to read in mem2, the
non-blocking read had no more data. What do you want to have happen?

You can't return an error value, because the istream is needed for the
syntactical chaining convention.

You could block and wait for the input, but then why are you using a
non-blocking IOstream? Probably you want to have your process carry
out some other tasks in the meantime.

You could set the failbit on the stream. This could be made to work,
but requires a fair bit of effort. You certainly will need a means to
convey back to the upper levels that this wasn't the same thing as a
format mismatch, the usual cause of a failbit; and you'll need to
unwind all the changes to the readin -- just like you were throwing an
exception. And, of course, you'll have to remember to check the failbit
and this new status every time you use it.

So, why not throw an exception? Conveniently enough, obj will be
incompletely read in, so you don't want it to be built, and you don't
want to consume the data on the nonblocking_iostream. The
nonblocking_iostream (or its streambuf) throws an exception, and on
unwinding, nonblocking_iostream operator>> backs its buffer up to some
previously marked point. This is a case where we deliberately would
want *non-virtual* dispatch to work for us; we use the derived class
operator at the points where the user knows about and can deal with the
non-blocking-ness, and then that just calls the usual iostream readin
functions. So the operator>> would look something like this:

template<class T>
operator>>(nonblocking_iostream &is, T &obj) {
  nonblock_iostream_sentry mark(is);
  static_cast<iostream &>(is) >> obj;
  mark.dismiss();
}

where the sentry's destructor rewinds the stream to its previous
position, unless it has been dismissed; the characters are only really
consumed when the last sentry is gone.

Note that on fail -- a misformatted stream but no exception thrown --
no rewind is done. (NB it would have to work right in the presence of
throwing basic_iostream behavior as well, but that's a detail.) A
knowledgeable calling routine could use the same idiom to try to
recover from such a case, but it needs knowledge.

Now, it is likely that doing this kind of thing frequently, as a
polling loop for example, will cause performance problems. So other
means need to be provided for checking the presence of 'enough data' in
tight inner loops. But in cases where, most of the time but not always,
you expect the input to be there, this could be a nice simplification
of the programmer's problem dealing with incomplete reads.

Comments on this idea? Is there something that I've missed that
prevents/argues against such an approach? If there is interest, I am
going to try to design the skeleton of such an iostream/streambuf pair
and see if it can be made to work.

George Heintzelman
georgeh_at_[hidden]


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk