Boost logo

Boost :

From: Christopher Kohlhoff (chris_at_[hidden])
Date: 2005-12-19 16:15:35


--- Jeremy Maitin-Shepard <jbms_at_[hidden]> wrote:
> It seems that a possible solution is to provide a `dummy'
> demuxer that simply makes asynchronous operations fail. This
> `dummy' demuxer could then be the default parameter to the
> socket constructors, thus eliminating the need to specify a
> demuxer for synchronous-only use.
>
> It seems that this would indeed clean up the interface for
> synchronous-only use.

After pondering this for a few days, I've come to the conclusion
that doing the above isn't that dissimilar to having a singleton
demuxer. Such a demuxer would be used as the default argument to
constructors, as Jeremy said. It certainly wouldn't be any
different in terms of runtime performance. What do people think
of this approach?

Basically I would add a new static function
basic_demuxer<>::global(), which returns a reference to the
singleton demuxer object. Constructors would look something
like:

  basic_stream_socket(demuxer_type& d = demuxer_type::global())

Synchronous-only code could then be written without knowledge of
the demuxer:

  ipv4::host_resolver r;
  stream_socket s;
  deadline_timer t;
  etc...

However, unlike Jeremy's dummy demuxer suggestion, the async
operations would still be allowed. This would benefit some async
apps that only want a single demuxer, since they no longer have
to pass it to everywhere it would be used. It would still be the
responsibility of the application to call run() on the singleton
demuxer if it does make async calls.

A further advantage of this approach is that, by allowing
default construction of the various resource classes, I can
implement swap() to allow the underlying resources to be passed
around efficiently without requiring additional dynamic memory
allocation.

[ And in a related idea, would it be worth implementing "move
constructors" like auto_ptr has for the resource classes? This
would allow users to create efficient factory functions that set
up default options, etc, e.g. something like:

  stream_socket make_stream_socket()
  {
    stream_socket s;
    ...
    return s.move();
  }

Or is this something better left out since it might be
non-portable, not compatible with standard C++, better covered
by some separate utility...? ]

I generally dislike singletons because of the issues to do with
the order of cosntruction and cleanup. The problem arises here
if, for example, there is a global stream_socket that uses the
singleton. I suggest that the singleton could be:

- Created at the first call to demuxer::global(), so that any
  errors (such as failure to initialise Winsock) can be thrown
  and caught in user code.

- Never destroyed. This is not good for DLLs that are
  dynamically loaded and unloaded, but perhaps the documentation
  can advise authors of such DLLs to avoid the singleton
  demuxer.

There may be a better way to manage creation and destruction, so
I'm open to suggestions.

Applications that need to initialise a demuxer in a particular
way, e.g. by setting custom allocation options, can do so by not
using the singleton demuxer.

As with some other features of the demuxer, this approach is not
totally unlike that used by std::locale. It doesn't require
significant changes to the current implementation of asio and
existing code should work as-is. It doesn't add new types that
could significantly increase the size of the API. It doesn't
promote sync over async or vice versa (for better or worse ;).
Other than the creation/destruction issues discussed above, I
can't think of any insidious, hard-to-find bugs that this
approach might cause to justify not doing this change.

Cheers,
Chris


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