Boost logo

Boost :

From: William E. Kempf (wekempf_at_[hidden])
Date: 2003-02-10 11:00:07


>
> From: "Peter Dimov" <pdimov_at_[hidden]>
> Date: 2003/02/10 Mon AM 09:08:08 EST
> To: "Boost mailing list" <boost_at_[hidden]>
> Subject: Re: [boost] Re: A new boost::thread implementation?
>
> David Abrahams wrote:
> > "Peter Dimov" <pdimov_at_[hidden]> writes:
> >
> >> I am not saying that this is never useful, but syntax should target
> >> the typical scenario, not corner cases.
> >
> > Agreed. I suppose that you'll say it doesn't target the typical
> > scenario because of its confusability. I wouldn't argue. Any other
> > reasons?
> >
> > What about:
> >
> > result(f)
>
> Unqualified? ;-)

Sorry, I don't understand this response.

> >> It makes a lot more sense (to me) to reserve operator() for the
> >> Runnable concept, since that's what Boost.Threads currently uses.
> >
> > And prevent any other concepts from using operator()? Surely you
> > don't mean that.
>
> No, I meant in that particular case.

I tend to agree with this.
 
> We have three concepts: Runnable, Executor (executes Runnables), and
> HasResult (for lack of a better name.) The AsyncCall concept I had in mind
> is both Runnable and HasResult, so it can't use operator() for both.
> x.result() or result(x) are both fine for HasResult.

I tend to prefer x.result() because it doesn't require a friend declaration. Adding a result(x) on top of that is certainly easy, and if we think it's useful enough to be provided by the library, I'd vote for providing both forms because of this.

> Here's some compilable code, to put things in perspective:

Thanks. This helps me, at least.

> #include <boost/detail/lightweight_mutex.hpp>
> #include <boost/function.hpp>
> #include <boost/bind.hpp>
> #include <stdexcept>
> #include <string>
> #include <iostream>
>
> template<class R> class async_call
> {
> public:
>
> template<class F> explicit async_call(F f): f_(f), ready_(false)
> {
> }
>
> void operator()()
> {
> mutex_type::scoped_lock lock(mutex_);
> new(result_) R(f_());
> ready_ = true;
> }

Hmm... is this truly portable? Don't you have to use the same techniques as optional<> here... or even just use optional<> in the implementation? Also, though not on subject with discussion of the design, it's probably a bad idea to lock the mutex in this way (mutexes shouldn't be held for extensive periods of time), though the alternative implementation requires 2 copies of R and a condition for waiting on the result.
 
> R result() const
> {
> mutex_type::scoped_lock lock(mutex_);
> if(ready_) return reinterpret_cast<R const &>(result_);
> throw std::logic_error("async_call not completed");
> }
>
> private:
>
> typedef boost::detail::lightweight_mutex mutex_type;
>
> mutable mutex_type mutex_;
> boost::function<R ()> f_;
> char result_[sizeof(R)];
> bool ready_;
> };
>
> int f(int x)
> {
> return x * 2;
> }
>
> int main()
> {
> // step 1: construct an async_call
> async_call<int> call( boost::bind(f, 3) );
>
> // 1a: attempt to obtain result before execution
> try
> {
> std::cout << call.result() << std::endl;
> }
> catch(std::exception const & x)
> {
> std::cout << x.what() << std::endl;
> }
>
> // step 2: execute an async_call
> call();

This example, and the implementation above, are just complex synchronous calls. I assume you really meant for either the constructor or this call to also take an Executor concept?
 
> // step 3: obtain result
> try
> {
> std::cout << call.result() << std::endl;
> }
> catch(std::exception const & x)
> {
> std::cout << x.what() << std::endl;
> }
> }

The one "issue" I see with using operator() to invoke the function is the race conditions that can occur if the user calls this multiple times. I'd consider it a non-issue personally, since you'd have to go out of your way to use this design incorrectly, but thought I should at least point it out.

Actually, there's another minor issue as well. The user can call operator() and then let the async_call go out of scope with out ever calling result(). Mayhem would ensue. The two options for dealing with this are to either block in the destructor until the call has completed or to simply document this as undefined behavior.

William E. Kempf
wekempf_at_[hidden]


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