The Callback Container or dispatcher maps a callback to an index. The index type is a template parameter, as shown in the following definition:
template <typename Signature, typename IndexType = int, typename DecisionStrategy = detail::always_true<IndexType>, typename RoutingStrategy = detail::default_routing<IndexType> > class dispatcher
The only required parameter is Signature
which defines the callback signature that can be registered to a dispatcher
instance. This is where the function signatures can be defined as shown in
the following examples:
boost::dispatch::dispatcher < void () > void_dispatcher; boost::dispatch::dispatcher < int ( int, int, double ) > retun_int_dispatcher; boost::dispatch::dispatcher < my_type ( std::string, my_other_type ) > my_type_dispatcher;
This makes the dispatcher a homogenous function registry -- where callbacks of the same signature can be registered. This means that all the functions registered to a dispatcher all take the same argument types. This allows you to group together similar functions that do different things and associate them by a certain index.
IndexType
should refer to a type which can be compared with the operator<
or at least, std::less<IndexType>::operator()(const IndexType &, const IndexType &)
is defined. |
When an index passed to the index operator of the dispatcher istance is
not bound to an actual callback, then an exception of type boost::dispatch::unregistered_handler
is thrown. In the
following example:
boost::dispatch::dispatcher<int ()> d; d[0] = a_function; d[1]();
The call to d[1]()
will
throw an boost::dispatch::unregistered_handler
exception because
there is no callback registered to the index 1. To handle this situation,
we wrap the invocation in a try { } catch (boost::dispatch::unregistered_handler &) { }
block.
try { d[1](); } catch (boost::dispatch::unregistered_handler & e) { std::cout << "Unregistered!" << std::endl; };
There is a convenience macro defined to define the default function to
wrap a dispatch call placed in the catch
block. This simplifies the invocation of dispatch calls, and is shown below:
BOOST_DISPATCH ( d[1]() , (std::cout << "Unregistered!" << std::endl ));
The convenience macro above makes code equivalent to the previous code snippet.
The index passed to the index operator of the dispatcher may be validated using the defined validation strategy. This is done by passing the validator type when creating an instance of the dispatcher.
struct string_validator { bool operator () (const std::string & s) const { return (s.size() >= 5 ); }; }; // ... boost::dispatch::dispatcher <void(), std::string, string_validator> validating_d;
The only requirement for the DecisionStrategy
parameter is the implementation of the function operator (operator()
)
which takes an argument type that's the same as the index type.
In the case when a callback is being accessed/indexed using an invalid
index based on the decision strategy, an exception of type boost::dispatch::invalid_index<IndexType>
is thrown. In the above example, an assignment to an index "1234"
will cause the operation to throw an exception of type boost::dispatch::invalid_index<std::string>
containing a copy of the offending
index.
try { validating_d["1234"] = function; } catch (boost::dispatch::invalid_index<std::string> & e) { std::cout << e.index << " is an invalid index!" << std::endl; }
The index validator type is intantiated in the dispatcher instance. The
instance can be accessed by clients by referencing the dispatcher instance's
_index_validator
member
variable. In case there are other methods defined in the index validator
type, those can be accessed with the _index_validator
.
The RoutingStrategy
template
parameter allows the dispatcher instance to either normalize or perform
transformative operations based on the given index parameter in the index
operator (operator[]
).
The strategy should be a type which is instantiated in the dispatcher instance.
The following snippet shows how the strategized index routing can be used
to perform a hash on the index.
struct hash_generator { unsigned int operator() (unsigned int input) { return input % 2; }; }; // ... boost::dispatch::dispatcher<void(), unsigned int, boost::dispatch::detail::always_true<unsigned int>, hash_generator> d; d[0] = even; d[1] = odd; BOOST_DISPATCH (d[99](), (std::cout << "This wouldn't be displayed..." << std::endl));
Since hash_generator
is
a type, it can be as state-full as possible. The following example shows
how a state-full routing strategy along with a index validation strategy
can be used to implement a rotisserie or circular callback implementation:
struct rotisserie { unsigned int index; rotisserie() : index (0) { }; unsigned int operator() (unsigned int) { return index++ % 8; }; }; // ... boost::dispatch::dispatcher<void (), unsigned int, boost::dispatch::detail::always_true<unsigned int>, rotisserie > d; d[0] = function1; d[1] = function2; d[2] = function3; d[3] = function4; d[5] = function5; d[6] = function6; d[7] = function7; d[8] () ; //function1 will be called; d[99] () ; // function2 will be called; d[0] () ; //function3 will be called;
Since the rotisserie instance in the dispatcher holds its own state, any
attempt to index the dispatcher using operator[]
will invoke rotisserie::operator()
.
The routing strategy instance is accessible from clients by accessing the
_router
member variable
of the dispatcher instance. So if the routing strategy type also defines
different methods, these can be also be invoked. In the above example,
other member methods of rotisserie can be accessed though d._router
.
Copyright © 2006 ,2007 Dean Michael Berris |