I l@ve RuBoard Previous Section Next Section

10.2 Overloading and the Catch-All Function

Although not germane to the Visitor pattern, leveraging C++ overloading (or not) has a tremendous impact on implementing designs that use Visitor.

In DocElementVisitor, we defined one member function per type visited: VisitParagraph(Paragraph&), VisitRasterBitmap(RasterBitmap&), and so on. These functions foster a kind of redundancy. The name of the element visited is also encoded in the function name.

Usually it's best to avoid redundancy. We can get rid of it by leveraging C++ overloading. We simply name all the functions Visit and leave it to the compiler to figure out which overload of Visit to call based on the type of the parameter passed to it. So an alternative DocElementVisitor definition looks like this:



class DocElementVisitor


{


public:


   virtual void Visit(Paragraph&) = 0;


   virtual void Visit(RasterBitmap&) = 0;


   ... other similar functions ...


};


It also becomes simpler to define all the Accept member functions. They now exhibit a nice uniformity.



void Paragraph::Accept(DocElementVisitor& v)


{


   v.Visit(*this);


}





void RasterBitmap::Accept(DocElementVisitor& v)


{


   v.Visit(*this);


}


They look so similar that you might be tempted to factor them out in the base class DocElement. Wrong. The similarity is only a mirage. Actually, the functions are quite different: The static type of *this in Paragraph::Accept is Paragraph&, and in RasterBitmap:: Accept it's RasterBitmap&. It is this static type that helps the compiler figure out which overload of DocElementVisitor::Visit to call. If you implement Accept in DocElement, the static type of *this would be DocElement&, which doesn't provide the compiler with the needed type information. So base class factoring is not an option. It actually invalidates our design.

Using overloading introduces an interesting idea. Let's assume that all DocElement derivatives implement Accept by simply bouncing to DocElementVisitor::Visit. Then we can provide DocElementVisitor with the following catch-all overload.



class DocElementVisitor


{


public:


   ... as above ...


   virtual void Visit(DocElement&) = 0;


};


When will this overload be called? If you directly derive a new class from DocElement and don't provide an appropriate Visit overload for it in DocElementVisitor, then overloading rules and automatic derived-to-base conversions come into play. The reference to the unknown class will be automatically converted to a reference to the DocElement base class, and the catch-all member function will be called. If you don't put the catch-all in there, you'll get a compile-time error. Whether you prefer this way or the other depends on the concrete situation.

You can do a lot of things inside the catch-all overload. Refer, for example, to John Vlissides' work (Vlissides 1998, 1999). You can take contingency measures, do something very generic, or fall back to a type switch (hack by using dynamic_cast) to find out the actual type of the DocElement.

    I l@ve RuBoard Previous Section Next Section