Runtime Dynamic Dispatch

PrevUpHomeNext

Callback Container (Dispatcher)

Unregistered Handlers
Strategized Index Validation
Strategized Index Routing

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()(constIndexType&,constIndexType&) is defined.

Unregistered Handlers

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.

Strategized Index Validation

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.

Strategized Index Routing

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

PrevUpHomeNext