
--- Jeremy Maitin-Shepard <jbms@cmu.edu> 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