I l@ve RuBoard |
![]() ![]() |
10.6 Hooking VariationsVisitor has a number of variations and customizations. This section is dedicated to mapping the hooks that you might need so that you can add these to the implementation that we've put in place. 10.6.1 The Catch-All FunctionWe discussed the catch-all function in Section 10.2. A Visitor may encounter an unknown type derived from the base class (in our example, DocElement). In this case, you may want either a compile-time error to occur or a default action to be carried out at runtime. Let's analyze the catch-all issue for the GoF Visitor and the Acyclic Visitor implementations provided by our generic components. For the GoF Visitor, things are quite simple. If you include the root class of your hierarchy in the typelist you pass to CyclicVisitor, then you give yourself the opportunity to implement the catch-all function. Otherwise, you take the compile-time error route. The following code illustrates the two options: // Forward declarations needed for the GoF Visitor class DocElement; // Root class class Paragraph; class RasterBitmap; class VectorizedDrawing; typedef CyclicVisitor < void, // Return type TYPELIST_3(Paragraph, RasterBitmap, VectorizedDrawing) > StrictVisitor; // No catch-all operation; // will issue a compile-time error if you try // visiting an unknown type typedef CyclicVisitor < void, // return type TYPELIST_4(DocElement, Paragraph, RasterBitmap, VectorizedDrawing) > NonStrictVisitor; // Declares Visit(DocElement&), which will be // called whenever you try visiting // an unknown type All this was quite easy. Now let's talk about the catch-all function within our Acyclic Visitor generic implementation. In Acyclic Visitor an interesting twist occurs. Although essentially catch-all is all about visiting an unknown class by a known visitor, the problem appears reversed: An unknown visitor visits a known class! Let's look again at our AcceptImpl implementation for Acyclic Visitor.
template <typename R = void >
class BaseVisitable
{
... as above ...
template <class T>
static ReturnType AcceptImpl(T& visited,
BaseVisitor& guest)
{
if (Visitor<T>* p =
dynamic_cast<Visitor<T>*>(&guest))
{
return p->Visit(visited);
}
return ReturnType(); // Attention here!
}
};
Suppose you add a VectorizedDrawing to your DocElement hierarchy in a sneaky way—you don't notify any concrete visitors about it. Whenever VectorizedDrawing is visited, the dynamic cast to Visitor<VectorizedDrawing> fails. This is because your existing concrete visitors are not aware of the new type, so they didn't derive from Visitor<VectorizedDrawing>. Because the dynamic cast fails, the code takes the alternate route and returns the default value of ReturnType. Here's the exact place where the catch-all function can enter into action. Because our AcceptImpl function hardcodes the return ReturnType() action, it quite rigidly imposes a design constraint without leaving room for variation. Therefore, let's put a policy in place that dictates the catch-all behavior: template < typename R = void, template <typename, class> class CatchAll = DefaultCatchAll > class BaseVisitable { ... as above ... template <class T> static ReturnType AcceptImpl(T& visited, BaseVisitor& guest) { if (Visitor<T>* p = dynamic_cast<Visitor<T>*>(&guest)) { return p->Visit(visited); } // Changed return CatchAll<R, T>::OnUnknownVisitor(visited, guest); } }; The CatchAll policy can do whatever the design requires. It can return a default value or error code, throw an exception, call a virtual member function for the visited object, try more dynamic casts, and so on. The implementation of OnUnknownVisitor depends largely on the concrete design needs. In some cases, you might want to enforce visitation for all the types in the hierarchy. In other cases, you might want to visit some types freely and to ignore all others silently. The Acyclic Visitor implementation favors the second approach, so the default CatchAll looks like this: template <class R, class Visited> struct DefaultCatchAll { static R OnUnknownVisitor(Visited&, BaseVisitor&) { // Here's the action that will be taken when // there is a mismatch between a Visitor and a Visited. // The stock behavior is to return a default value return R(); } }; Should you need a different behavior, all you have to do is plug a different template into BaseVisitable. 10.6.2 Nonstrict VisitationMaybe it's human nature, but the have-your-cake-and-eat-it-too ideal is every programmer's mantra. If possible, programmers would like a fast, noncoupled, flexible visitor that would read their minds and figure out whether an omission is a simple mistake or a deliberate decision. On the other hand, unlike their customers, programmers are practical, down-to-earth people, with whom you can haggle about trade-offs. Following this line of thought, the flexibility of CatchAll renders users of GoF Visitor envious. As it is now, the GoF Visitor implementation is strict—it declares one pure virtual function for each type visited. You must derive from BaseVisitor and implement each and every Visit overload, and if you don't, you cannot compile your code. However, sometimes you don't really want to visit each type in the list. You don't want the framework to be so strict about it. You want options: Either you implement Visit for a type, or OnUnknownVisitor will be automatically called for your CatchAll implementation. For this kind of situation, Loki introduces a class called BaseVisitorImpl. It inherits BaseVisitor and uses typelist techniques for accommodating typelists. You can look up its implementation in Loki (file Visitor.h). ![]() |
I l@ve RuBoard |
![]() ![]() |