|
Boost : |
From: Doug Gregor (gregod_at_[hidden])
Date: 2001-04-10 18:30:22
On Tuesday 10 April 2001 07:55, you wrote:
> At 8:34 PM +0300 4/10/01, Peter Dimov wrote:
> From: "Jesse Jones" <jesjones_at_[hidden]>
>
> >> >if(f) f();
> >>
> >> 1) It parallels what you would do with an ordinary function pointer.
> >> 2) Why call a function if it's empty?
> >
> >Yes, this makes sense. But introducing undefined behavior still feels
> >somewhat wrong to me. Undefined behavior is usually introduced when
> > validity checks (a) will have severe performance impact, or (b) are
> > impossible. Neither of these applies.
>
> The problem is that as a library author you can't determine if
> checking preconditions in release is going to have a "severe
> performance impact". You have no idea how sensitive the app is to
> small performance hits and no idea how often clients are actually
> calling your function. Throwing an exception, asserting, crashing,
The precondition check is very cheap - a single null pointer check. The cost
of invoking an any_function is at least the cost of copying the arguments
(once for any_function, once for the invoker, and perhaps one more time) plus
the cost of invoking at least one function through a dynamically resolved
address.
In the case of, for instance, vector::operator[] I understand why no checking
is done: that adds two conditionals to an operation which is essentially just
an add. However, in the case of any_function, the precondition cost is
dwarfed by the cost of normal operation, so I see no reason to trigger a
fatal error when it costs so little to give better error handling.
The only hole I can see in my argument so far is that having the "throw"
could perhaps cause some compilers to produce poor code. Perhaps this is
enough to warrant using a policy class.
Perhaps we can use this to our advantage. When calling
any_function::operator(), perhaps it should be done as such:
policy.pre_call(*this);
return policy.post_call(*this, invoker(functor, arg1, arg2, ..., argN));
The default policy could be:
struct default_function_policy
{
template<typename R, typename T1, ..., typename TN>
void pre_call(const any_function<R, T1, ..., TN>& f)
{
if ( f.empty() )
throw no_function_target(&f);
}
template<typename R, typename T1, ..., typename TN>
void post_call(const any_function<R, T1, ..., TN>&,
typename any_function<R, T1, ..., TN>::result_type r)
{
return r;
}
// for any_function objects returning void
template<typename R, typename T1, ..., typename TN>
void post_call(const any_function<R, T1, ..., TN>&) {}
};
This gives the user preference over checking - they can check if they want
and perform any action they wish. This also gives an easy way to add a
synchronization barrier around invocations of an any_function:
struct synchronized_function_policy
{
mutex m;
template<typename R, typename T1, ..., typename TN>
void pre_call(const any_function<R, T1, ..., TN>& f)
{
m.lock();
}
template<typename R, typename T1, ..., typename TN>
void post_call(const any_function<R, T1, ..., TN>&,
typename any_function<R, T1, ..., TN>::result_type r)
{
m.unlock();
return r;
}
// for any_function objects returning void
template<typename R, typename T1, ..., typename TN>
void post_call(const any_function<R, T1, ..., TN>&)
{
m.unlock();
}
};
Doug
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk