This column is going to be
something of a transition. First, I want to wrap up the
discussion of Indirect Visitor that I started two columns ago
[1,2] and which has now gone on much longer than I intended. Then
I want to step back and take a look at some of the my older
columns and maybe make a few corrections or comments that seem
justified now that they have had a chance to age somewhat.
Finally, I'll provide a brief overview of what I plan to cover in
the next year or so.

First allow me to apologize to
those readers who have not read and do not have immediate access
to the previous columns that I will be mentioning. While I would
wish that every column stand on its own, sometimes that is
difficult. In this case, it is virtually impossible. I will
summarize the other columns as best I can, but there is no way I
can present more than a sentence or two. To try and mitigate this
situation somewhat, I have posted copies of all my past columns
on my web site at www.bleading-edge.com.

Wrapping up Indirect Visitor

In [1] I presented a variation of
the Visitor pattern (see Design Patterns [3]). That version of
Visitor used the map<> template from the Standard
C++ library to provide a simulation of a virtual function table.
The map<> key was a type_info* obtained by
using the typeid() operator. The value part of the map<>
was a pointer to a visit function. As I noted in [1], that
version was not as well received as I had expected. My coworkers
worried about the overhead of the map<> class and
felt that no matter how low it might be, it was obviously higher
than the standard visitor class. The question was "why pay
for the runtime overhead if you didn't have to?"

So I revised Indirect Visitor in
[2] to use a vector<> instead of the map<>.
In this case the index of the vector is a unique identifier for
each class in the node hierarchy. The identifier is generated by
a set_uid() function defined in the base class of the
hierarchy. This scheme actually works very well, but there are a
few idioms that I want to point out. All of these are applicable
to either version.

First, suppose you wish to
override a visitor function in a derived visitor to add some
functionality, but then invoke the base class' version. In a
normal derived class you would do something like

MyClass::foo()

{

// do something

inherited::foo();

}

[Note: the identifier inherited
is a typedef for the base class' name. This idiom is
described by Stroustrup in D&E [4] and I use it regularly].
Since the visit functions in Indirect Visitor are not member
functions, a different form is required.

MyVisitor::Impl::visit_foo(Visitor&
v, Object& o)

{

MyVisitor mv& =
down_cast<MyVisitor&>(v);

// do something

mv.inherited::visit(o);

}

The unusual form of the call to visit()
is required to prevent visit() from being invoked as a
virtual function. Alternatively, you could write

MyVisitor::Impl::visit_foo(Visitor&
v, Object& o)

{

// do something

VisitFunction f =

o.look_up(inherited::function_table());

f(v, o);

}

Of course, this is exactly what
the call to inherited::visit() does. The two things to
avoid is looking up the function pointer directly as in

VisitFunction f =
inherited::function_table()[Foo::UID];

This will return the function
pointer for Foo in the base class's function table, but
this could be a null pointer. You might as well let the look_up()
function in Foo handle that check. The other problem is
invoking the visit function virtually as in

MyVisitor::Impl::visit_foo(Visitor&
v, Object& o)

{

MyVisitor& mv =
down_cast<MyVisitor&>(v);

// do something

inherited& iv = mv;

iv.visit(o);

}

This is just a recursive call to MyVisitor::visit().

In this particular case, you want
to make sure that a call to visit() is not a virtual call.
In another case, you want to insure just the opposite. In my
example (based on Meyers [5]) I imagined a hierarchy of outer
space game objects. One of my GameObjects was a SpaceDock.
As SpaceDock object contained a (possibly empty)
collection of SpaceShip objects. It is easy to imagine
that a standard PrintVisitor::visit_space_dock() function
would print out some information about the SpaceDock
itself, then iterate over the SpaceShip collection and
visit each SpaceShip object in turn. A simple version
might look like

PrintVisitor::Impl::visit_space_dock(Visitor&
v,

GameObject& o)

{

PrintVisitor& pv =
down_cast<PrintVisitor&>(v);

SpaceDock& sd =
down_cast<SpaceDock&>(o);

pv._os << "Visiting
Space Dock" << sd.name() << \n;

std::for_each(sd.list().begin(),
sd.list().end(), pv);

}

In this case, the for_each()
function invokes the visitors operator()() function, which
in turn calls visit() as a virtual function. This way,
someone can derive a new visitor from PrintVisitor, supply
visit functions for various types of SpaceShips, but not
have to override the SpaceDock visitor function. Now you
see one of the reasons why I have my Visitor base class provide
an operator()() function as well as visit().

I wrapped up [2] with a slightly
contrived example of how to do multiple inheritance with the
Indirect Visitor. The resulting class tree is shown in figure 1.
In testing this idea, I made an interesting discovery. MyPrintVisitor
inherits from both AnnsPrintVisitor, and BobsPrintVisitor.
Since neither Ann nor Bob declared PrintVisitor a virtual
base class of their visitors, the resulting object of type MyPrintVisitor
contains two copies of both PrintVisitor and Visitor.
MyPrintVisitor overrides any function that is unique to
either BobsPrintVisitor or AnnsPrintVisitor with a
forwarding function. That function uses a dynamic_cast to
recover a reference to MyPrintVisitor, and then invokes
either Ann's or Bob's visitor as necessary. If a slot in the
function table was identical in both Ann's and Bob's visitors,
then I assumed that the function was a PrintVisitor
function, and just left it alone in MyPrintVisitor's
table.

When I tested this, I used a
debugging version of PrintVisitor and ran into an
interesting situation. In PrintVisitor visit function
first tries to recover the PrintVisitor object reference. A
typical snippet of code might look like

PrintVisitor::Impl::visit_asteroid(Visitor&
v,

GameObject& o)

{

PrintVisitor& pv =
down_cast<PrintVisitor&>(v);

As I described in my RTTI column
[6], the down_cast template actually does a dynamic_cast
during debugging, but switches to a static_cast for
released libraries. In this case, the dynamic_cast of 'v'
first recovers the underlying MyPrintVisitor object
reference. It then attempts to convert that to a PrintVisitor
reference, but since there are two PrintVisitor objects in
a MyPrintVisitor, the cast is ambiguous and throws a bad_cast
exception. Switching to a release version of PrintVisitor
produced the results I expected. This is the first case I am
aware of where a static_cast is the preferred over a dynamic_cast
for functionalitys sake and not just for performance
reasons.

Since it was concerns about the
performance of the map<> based version of Indirect
Visitor that led directly to the current version, I can not
resist a brief look at the performance of Indirect Visitor
compared to the standard Visitor. The standard visitor has the
following overheads (assuming a do-nothing visit function):

virtual function call -- accept()

virtual function call --
visit_xxx()

return

return

In comparison the Indirect
Visitor has these overheads

virtual function call -- visit()

function call -- function_table()

return

virtual function call --
look_up()

indexed table look up

test for null pointer

return

function call -- visit_xxx

return

return

On the assumption that a typical
C++ implementation of a virtual function call is equivalent to an
indexed table lookup followed by an ordinary function call, then
the Indirect Visitor has 3 virtual function calls, a normal
function call, and a null pointer test versus 2 virtual function
calls for the standard Visitor pattern. Rounding things off, I
would say that Indirect Visitor has about twice the overhead of
the standard Visitor. Given any functionality at all in a typical
visit function (and most of my visit functions do conversions
from one data format to another), then I think it is safe to say
that Indirect Visitor has overhead which is comparable to the
standard Visitor pattern.

I discussed the advantages of
Indirect Visitor in [1] and [2], but I also mentioned that there
are some disadvantages to using Indirect Visitor. These can be
summarized in one word: complexity. When building a specific
visitor, the programmer has to do things that ordinarily are left
up to the compiler. This is tedious, and can be error prone, and
the compiler will not be able to catch your mistakes. For
example, suppose you add a new class to the node hierarchy, and
forget to include a definition of the class's UID in the class'
implementation file. With a Standard C++ system, this will cause
a link-time error. With Cfront, and those implementations which
attempt to maintain compatibility with Cfront, the lack of a
definition for a static member will go completely unnoticed. If
you get the linker error and add the definition of UID, but do
not initialize it using the set_uid() function, then the
system will silently give your new node class a UID of 0. To help
check against this type of error, I arranged for all UID's to
begin with 1. If a system has correctly initialized all UID's,
then the zero'th slot in the visitor function table with always
be a null pointer. I check for this with an assert in the init()
function.

There are a numerous other
mistakes that can occur when setting up a visitor function table.
Some of these you can test for, and some you can not. On my real
version of Indirect Visitor, I created several test functions as
static members of the Visitor base class. Besides the check for
node UID initialized to zero, the one I use regularly checks to
make sure that all non-null function pointers in a table are
unique. This catches a certain type of editing error. Obviously,
the dynamic_casts in the individual visit functions will
also turn up certain types of errors, but this requires more
extensive debugging. The obvious point is your testing should
actually invoke every visit function in your table. You can no
longer assume that just because you wrote it, that you can
actually invoke it.

Overall, however, I have found
that Indirect Visitors are not that hard to create and debug, and
the flexibility is certainly appreciated. If you have actually
studied the standard Visitor pattern in more than a casual
manner, you may have a few questions about why I chose this
approach. Some of the questions that I have asked myself include:

a. John Vlissides presents a
"trapdoor" mechanism for the standard Visitor pattern
in [7]. This allows a node hierarchy to be extended without
having to recompile everything. Did I consider that mechanism?
Yes, I did, but I consider the "trapdoor" a temporary
means of adding one or two classes to an existing hierarchy until
it is convenient to change the hierarchy and recompile all the
libraries. The Indirect Visitor is a more general purpose
mechanism, though it does take some work to set up correctly.

b. Did I consider the Acyclic
Visitor pattern? The Acyclic Visitor is described by Dr. Martin
in [8]. I must admit that I was not aware of the Acyclic Visitor
until Dr. Martin and I were discussing Visitor's over a couple of
beers in New York. I really like the Acyclic Visitor, and I find
it fascinating that we have two variations of Visitor which
address the same problem, but do so in totally different ways.
Acyclic Visitor uses multiple inheritance and RTTI for its
solution. As such, it is a lot easier to use since it depends
upon standard features of the language. Nevertheless, I get the
feeling that Acyclic Visitor is best suited for a different kind
of problem than Indirect Visitor. I would recommend that
developers have both variants (in fact, all the variants they can
find) of Visitor in their toolkit, and use the one that seems
most appropriate for the problem at hand.

c. Did I consider replacing my
original map<> with a hash table? Ah, now this is
the question. The simple answer is that I actually argued that if
the log-n overhead of a map<> was too high, then a hashed_map<>
was the logical replacement, but when it came time to actually
replace map<> with something better, I didn't have a
hash_map<> template lying around. If you think that
is a rather weak excuse, so do I. I mention it just to make the
point that just because something is not part of the Standard
Library is no excuse for not having it available when you need
it. There are several good implementations of hashed_map<>
available, as well as numerous other useful class libraries, and
if you do C++ development for a living, you owe it to yourself
and your company to check them out. My real excuse is that at the
time one of my platforms was still based on Cfront, and I had no
idea whether the versions of hashed_map<> that I
could get would work with Cfront. Furthermore, I didn't want to
take the time to find out. That has changed, and in retrospect, I
would argue that under most circumstances the best way to
implement Indirect Visitor is by using a hashed_map<>.

One final note: the form of the look_up()
function reminded me of what conceptually happens when you send a
message to an object in a symbolic language. First the object's
class is checked to see if it has a method that can handle the
message. If not, then the object's immediate base class is
checked. This lookup continues until either a method is found, or
the root base class is reached without finding a message handler,
in which case an exception occurs. I am a firm believer in the
adage that if you want to program in a symbolic language, then
use a symbolic language. Nevertheless, sometimes you can use the
flexibility of a symbolic language, even though you are
programming in C++. The Indirect Visitor provides a reasonable
facsimile of that flexibility. The nodes in the hierarchy
represent the classes in a symbolic language, and hold the data.
The visitors represent the methods of the classes, with the data
in the visitor objects being the parameters of the methods. This
is hardly an easy to use pattern, but it does allow you to add
new classes and new methods for those classes anywhere and at
anytime without having to recompile the world.

Looking backward

Over the past couple of years, I
have made a few mistakes that I would like to straighten out.
Elsewhere in this magazine I have addressed my faux pa with the
example program I put together to introduce my column on Low
Overhead Class design [9], so I will not go into that here.

I was taken to task after my
column on RTTI [6] about my conclusions regarding the performance
of a dynamic_cast when implemented by a real compiler. I
mentioned in the column that a compiler would certainly not be
comparing strings as my home grown version had to do, but I did
not think through how a compiler might actually implement dynamic_cast.
The first point is that a compiler will probably not be using a
linear search of base classes; it will probably use a hash table.
Right off the bat this moves things into the realm of being a
constant time operation. Much more to the point, it was pointed
out to me that a compiler can reasonably assume that if one
attempt has been made to dynamically cast type A to type B, then
other attempts will likely occur. This means that it is
worthwhile for the compiler to cache the results of a dynamic_cast.
If this is done, and I am assured that good compilers do this,
then effectively the performance of ANY dynamic_cast in a
program can be considered amortized constant time. Naturally Order(1)
is not the same as Order(0), but for all practical
purposes, any dynamic_cast will have pretty much the same
overhead as any other.

I have to mention that in spite
of probably being a couple of orders of magnitude slower than a
real version of dynamic_cast, the overhead of my home
grown version is undetectable in practice. This leads to the
conclusion that the correct use of RTTI will probably not add any
noticeable performance penalty to an application. Just like
virtual functions, concerns about the overhead of RTTI turn out
to be unfounded in all but the rarest of circumstances. If my
remarks in any way contributed to those groundless concerns, I
hope I have corrected the situation.

One final little correction: in
my column on moving to Standard C++ [10] I mentioned the typical
practice of using forward declarations of classes such as ostream
and string just so you could declare a reference or
pointer. I pointed out that since these classes are not classes
in the Standard library anymore, but rather are typedefs
for complex templates, you can not be able to use the forward
declarations any longer. What I did not mention is that the
Standard Library provides a standard forward declaration for the iostream
classes in header <iosfwd>. Unfortunately, there is no
comparable header for string, but you could probably
create your own.

My biggest mistake in that column
[10] was predicting that Standard C++ compilers would be
appearing Real Soon Now. At that time, the second Committee Draft
of the Standard had been available since Nov 1996, and I thought
that was pretty close to a finished standard. I must have been
wrong, because even though the final draft of the Standard was
approved in November 1997, I still do not see the flood of
Standard compliant compilers that I expected.

Some vendors are making real
efforts, however, and so I have hopes that we will see real ISO
Standard C++ compilers and libraries by the end of 1998.
Unfortunately, 1997 left me somewhat discouraged and cynical
about the rate of progress on that front, so I am not making any
bets one way or another.

Looking forward

While ISO Standard C++ compilers
may be rather slow in appearing, I am fairly sure that they will
eventually arrive. In the meantime, however, compilers are
appearing which support more of the new features of the Standard.
Also, more and more vendors are starting to ship versions of the
Standard Library with their C++ compilers. As a result, I will
make two predictions about what will happen as programmers start
to use Standard C++ features and the Standard C++ library on a
regular basis.

The first thing I predict is that
people will find that it is easier to get their code correct. The
Standard library provides numerous tools that make it easier to
avoid many of the traditional sources of errors which ordinarily
occur in writing C/C++ code. In fact, many people may have that
sublime experience of creating some complex real world piece of
software and having it work correctly the first time they get it
to compile. Of course, many of them may not notice this right
away because getting things to compile using Standard C++ may
take more of an effort than they were originally use to. This is
especially true when dealing with templates  the error
messages can baffle the heck out of you. Nevertheless, those
templates can do an awful lot for you.

The second thing I predict is
that as more and more people actually start to develop real world
applications using the Standard C++ library, there will be some
percentage that discover that their version of the Standard C++
library is not adequate for their application. This should be a
fairly obvious conclusion  "one size fits all"
doesnt work in software anymore than it does in other
design arenas. Nevertheless, many people will find themselves
rather annoyed when the solution their attempted solution turns
out not to work very well. I know; Ive been there.

This second prediction will be
the basis for most of my columns in the near future. The general
theme might be called "Using the Standard C++ library",
but the subtitle might also be "What to do when things
dont go right". The Standard C++ Library is much more
than just a big collection of classes that we might choose to use
in our applications. First, and foremost, the Standard C++
Library is a set of abstractions. Obviously, we expect our
vendors to provide reasonable implementations of those
abstractions; and for the most part that is exactly what we will
get. Unfortunately, a lot of developers want to throw out the
abstraction itself, when it turns out that the implementations we
have are not reasonable for a specific need. Much of what I will
be showing is how to stay within the abstraction, but tailor the
solution to better fit a specific need. Next time, Ill
start with some variations of the "string" class.