Boost logo

Boost :

From: Douglas Gregor (gregod_at_[hidden])
Date: 2002-02-25 16:57:48


On Monday 25 February 2002 03:59 pm, you wrote:
> My biggest complaint is: Why support return values for signals?
>
> The problems I see with return values are as follows:
> 1) What is the return value, if you call a empty signal?

The return value for an empty signal is defined by the combiner for that
signal.

> 2) I like signal0 better then signal0< void >

Unfortunately, even without the return value specified, 'signal0' will still
be a template, so we'd be stuck with signal0<>.

> 3) They are not needed.
> I have been using signals for some years now and
> have never need return values. If needed they can be
> emulated by having a pointer in the signal arguments.

The best example of return value use I have is the generic 'resource
acquisition' example, where a signal 'X' is the entry point to acquiring a
resource from anyone will will provide it. The providers are slots that will
return either:
  1) A pointer to a 'resource acquired' block containing information needed
to access the resource
  2) A nil pointer, indicating that this provider can't/won't provide the
resource.

With return values, we can define and use X like this:
-----------------------------------------
signal1<resource_acquired*, const resource_request&, acquire_resource> X;
X.connect(provider1);
X.connect(provider2);
X.connect(provider3);

resource_request req;
if (resource_acquired* resource = X(req)) {
// use resource
}
-----------------------------------------

For illustration, the acquire_resources combiner looks like this:

struct acquire_resources {
  typedef resource_acquired* result_type;

  template<typename InputIterator>
  inline resource_acquired*
  operator()(InputIterator first, InputIterator last) const
  {
    for(/* no init */; first != last; ++first) {
      if (resource_acquired* acq = *first)
        return acq;
    }
    return 0;
  }
};

The equivalent to this example when signals can't have return values is a big
uglier. We don't get the direct 'if (resource_acquired* resource = X(req))'
syntax, and it is actually impossible to write the combiner
'acquire_resources' because it doesn't have access to the arguments being
passed to the slots.

I think in this case the return values make it possible to simplify the use
of signals, and that the workaround would produce uglier code.

> 4) Implementation and use will be simpler without then.

This depends - if there were no return values, would combiners also
disappear? If so, then the implementation would be much simpler. Otherwise,
the return values are entirely handled by the combiners, so the removal of
return value capabilities wouldn't simplify the code much.

> Some comments for the documentation:
>
> 1) I believe the slots is signalled in the sequence defined
> by the SlotNameCompare function. But I could not find answers
> for the following questions?
> a) Is the signals with 'low' names called first?
>
> b) What about unnamed connections or connections with the
> same name, are they signalled as first connected first
> signalled, last connected last signalled, or is the
> sequence undefined?

The answer to these is intentionally left unspecified, but it deserves a
comment in the tutorial: there are no guarantees about ordering of named
connections with respect to each other, or between named and unnamed
connections.

> 2) I have guessed that the default Combiner is last_value, but
> I could not find it in the documentation.

It's not in the documentation. I was content to bury last_value in a detail
namespace, but it looks there is interest in moving it into a more
user-visible location. I'll document that the default combiner is last_value.

> 3) What happens if you disconnect while in a signal-loop?
> Is it safe?

This definately needs documentation, I'm just not sure how to document it
well :(. In the single-threaded case, it is safe to disconnect at any time.
So something like this is legal and safe:

sig1 calls slotA
slotA calls sig1
sig1 calls slotB
slotB deletes sig1

> 4) What happens if you connect while in a signal-loop?
> Will the new slot be called?

That's unspecified. I'll note it in the documentation.

> 5) I believe the documentation for ~Connection should be:
> Effect: if ( this->is_controlling() ) this->disconnect();

Thanks. The poorly-named "controlling" behavior will actually be split into
separate connection classes.

> 6) In the tutorial I would like to see a example where the slot
> is a member function.

Okay, I'll add that.

> 7) I would like to have the Complexity of more functions documentated.
> It is surprising that the complexity of signal::empty()
> is O(n), where n is the number of slots in the signal.
> Maybe it would be better to have a signal::size() function.

Okay. empty() can be written as a constant time operation, if an active slot
count is added to a signal. Would you prefer that?

> Last, I will like to say that I am really impressed with the
> signal library. I just hope it wont crash my compiler (MSVC)
> too often.

Thanks! I think if Boost.Function is stable on MSVC, Signals will also be
stable. Signals (by itself) puts much less stress on the compiler than
Function.

        Doug


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