Boost logo

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