|
Boost : |
From: Jonathan Turkanis (technews_at_[hidden])
Date: 2004-09-08 22:41:17
"Jonathan Graehl" <jonathan_at_[hidden]> wrote in message
news:413F7C5A.1030701_at_graehl.org...
> >
> >
> > struct alphabetic_input_filter : public input_filter {
> > template<typename Source>
> > int get(Source& src)
> > {
> > character c;
> > while ((c = boost::io::get(src)).good() && !isalpha(c))
> > ;
> > return c;
> > }
> > };
> >
> >
> >
>
> (boost::io::)character is just a type that wraps an int with a good() test?
int_type or optional<char> would be good enough for that. The important point is
that there are two separate 'not good' states -- one means end-of-sequence and
the other means no data is currently available.
> >Here, eof and fail values are passed on to the caller unchanged. If you want
to
> >send an eof or fail notification explicitly, you'd write return eof() or
return
> >fail().
> >
> >Now the big question: is the above formulation too convoluted to teach to an
> >average user who is interested only in plain, blocking i/o?
> >
> >
> >
> That seems fine. For such a user, this is just boilerplate code pasted
> from documentation examples.
What worries me is that users already know about char, int and EOF. To use
character properly probably requires more than just copying from examples.
> If you really care to make filters written by naive users via a simple
> blocking interface applicable to more advanced nonblocking scenarios,
> you can design a generic adapter that turns a blocking filter into a
> nonblocking one. That is, it would wrap both the upstream and
> downstream, I imagine with a dynamically growing buffer that will accept
> any single output the naive user wants to produce, but then flagging
> when its downstream consumer doesn't accept the entire amount, and
> returning the EAGAIN equivalent to its upstream instead of calling the
> user's naive blocking method. This strategy would require a close
> notification since the buffered unaccepted stuff would be left alone
> until the next write attempt (unless you want to spawn a thread to spin
> attempting to empty the buffer).
I hadn't thought of that. I'd much rather teach people how to write correct
filters, so they can be used with maximum efficiency. There's also no good way
to turn a blocking input filter into a non-blocking one -- unless you consider
that every blocking input filter is sort of a degenerate non-blocking filter.
> About the close() or open() methods for a filter that wants to write
> some prelude or coda (e.g. gzip): aren't these only necessary because
> you can't guarantee that the constructors and destructors for the filter
> stack are called in the proper order?
No -- filters should be reusable. Here's an example from a reply I wrote to Rob
Stewart (it turned out not to be relevant to that discussion, but maybe it'll be
relevant here ;-).
"Jonathan Turkanis" <technews_at_[hidden]> wrote in message
news:chalsi$cmb$1_at_sea.gmane.org...
> struct zlib_ostream : filtering_ostream {
> zlib_ostream() { push(zlib_compressor()); }
>
> template<typename Source>
> zlib_ostream(const Source& src )
> {
> push(zlib_compressor());
> open(src);
> }
>
> template<typename Source>
> void open(const Source& src )
> {
> BOOST_STATIC_ASSERT(is_resource<src>::value);
> push(src);
> }
>
> bool is_open() const { return is_complete(); }
>
> template<typename Source>
> void close(const Source& src )
> {
> assert(is_open());
> pop();
> }
> };
int main()
{
using namespace boost::io;
zlib_ostream out;
out.open(file_sink("hello_world"));
out << "hello world!";
out.close();
out.open(file_sink("goodbye_world"));
out << "goodbye world!";
}
Only one zlib_compressor is constructed, but it is used several times.
> It would be nice if source -> A
> -> B -> sink could guarantee that B is (finally) constructed after sink,
> and then A is constructed after having been linked to B. That is,
> aren't the filters passed by const reference and only copy constructed
> once into the filter stack? I guess the part about ensuring that A is
> linked to B before the user constructor code might be accomplished by
> inheritance (superclass constructors always complete before subclass
> constructor executes, and the reverse for destructors?) I'm not sure if
> this is too clever, or can't be made to work portably, though.
Actually, I can run the destructors in any order I want, since I'm using
boost::optional<Filter> to avoid requiring that filters and resources be default
constructible. So I can just do filter_ = none;
> I don't think a second, simpler interface would be that much of a win;
> the complexity of having two interfaces or types of filters would add as
> much confusion as it simplifies the blocking case.
I've lost you here. Which is the 'second, simpler interface' which you don't
think is a good idea?
Best Regards,
Jonathan
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk