2

This is somewhat hypothetical as I'm not too worried about performance - just wondering which option is actually the fastest/most efficient in general, or if there is no difference whatsoever.

Suppose I have the following code for a visitor template that supports overloading:

#define IMPLEMENT_VISITOR_WITH_SUPERCLASS(superclass)  \
    typedef superclass visitor_super_t;     \
    virtual void visit(Visitor& v) { v.visit(*this); }
//-----------------------------------------------------------------------------
// Implementation detail:
// Selective dispatcher for the visitor - required to handle overloading.
//
template <typename T>
struct VisitorDispatch {
    static void dispatch(Visitor* v, T* t) { v->visit(*t); }
};
// Specalization for cases where dispatch is not defined
template <> struct VisitorDispatch<void> {
    static void dispatch(Visitor* v, void* t) { throw std::bad_cast(""); }
};

//-----------------------------------------------------------------------------
// Derive visitors from this and 'Visitor'.
template <typename T>
class VTarget
{
public:
    // Don't really need a virtual dtor.
    virtual void dispatch(T& t) = 0;
};

//-----------------------------------------------------------------------------
class Visitor
{
public:
    virtual ~Visitor() = 0;

    template <typename T>
    void visit(T& t) {
        typedef VTarget<T> target_t;
        target_t* tgt = dynamic_cast<target_t*>(this);
        if (tgt) {
            tgt->dispatch(t);
        }
        else {
            // Navigate up inhertiance hierarchy.
            // requires 'super' to be defined in all classes in hierarchy
            // applicable to this visitor.
            typedef typename T::visitor_super_t super;
            super* s = static_cast<super*>(&t);
            VisitorDispatch<super>::dispatch(this, s);
        }
    }
};

//-----------------------------------------------------------------------------
inline Visitor::~Visitor() {}

This is then used to create generic visitors:

class CommonBase { 
    IMPLEMENT_VISITOR_WITH_SUPERCLASS(void)
    virtual ~CommonBase() = 0;
};
class A : public CommonBase {
    IMPLEMENT_VISITOR_WITH_SUPERCLASS(CommonBase)
};
class B : public CommonBase {
    IMPLEMENT_VISITOR_WITH_SUPERCLASS(CommonBase)
};

class MyVisitor
    : public Visitor
    , public VTarget<CommonBase>
    , public VTarget<A>
    , public VTarget<B>
{
public:
    virtual void dispatch(CommonBase& obj);
    virtual void dispatch(A& obj);
    virtual void dispatch(B& obj);
};

Using the visitor ultimately results in dynamic_cast<>'s from Visitor to VTarget<T>, which is a cross-cast.

The other way this could be implemented is to make Visitor a virtual base of VTarget<T> - MyVisitor would then not need to inherit directly from Visitor anymore. The dynamic_cast<> in the Visitor::visit code would then result in a down-cast from the virtual base, Visitor.

Is one method faster than the other when performing the casts? Or do you only get a size penalty for having the virtual base?

Pete
  • 4,784
  • 26
  • 33
  • 3
    "Is one method more faster than the other when performing the casts?" The only sensible way to answer this is to actually measure. Good luck with that because it depends on, err, the code around. Voting to close. – Alexandre C. Aug 04 '11 at 11:48
  • 2
    Also, the whole point of visitor is that you don't need casting. – Alexandre C. Aug 04 '11 at 11:49
  • @Alexandre The reason that this visitor is implemented this way is that you don't have dependencies on the visitor targets in the class hierarchy. That way part of the class hierarchy can reside in a library and another part in client code - not really possible with a conventional static visitor. – Pete Aug 04 '11 at 11:55
  • I guess what I am really asking is: for a typical dynamic_cast implementation, would the downcast from a virtual base be faster than a crosscast? – Pete Aug 04 '11 at 11:56
  • You already get a big size penalty: typically one pointer for each base class in the `MyVisitor` class. Also, if the visitors are in a shared library, the template instantiation of `visit` being in client code, then usually the `dynamic_cast` (either method) is done via string comparison. But again, this question cannot be answered without measuring the impact on a real program using either approach. – Alexandre C. Aug 04 '11 at 12:18
  • To be clear to others - you only get a pointer per base when inheriting from a virtual base class. Also, a more typical use for this would be from a static library. – Pete Aug 04 '11 at 13:29
  • Each `VTarget` you inherit from will need its own vtable. – Alexandre C. Aug 04 '11 at 13:30
  • Sorry - yes you get a vtbl per base, but in the virtual base situation you get two pointers per base. – Pete Aug 04 '11 at 13:56

1 Answers1

1

Well, it looks like the cross-cast method is faster than the virtual base method.

With a visitation that requires 1 fallback to a superclass, over 100000000 iterations, the cross-cast method took 30.2747 seconds, and the virtual base method took 41.3999 - about 37% slower.

With no fallback to an overload for a superclass, the cross-cast was 10.733 seconds and the virtual base 19.9982 (86% slower).

I was more interested to know how the dynamic_cast would operate in either mode, really.

Pete
  • 4,784
  • 26
  • 33
  • Did you try the same code with visitor code in a shared library (.dll or .so) and the client code in the main program ? My bet is that it changes things a lot. Also, try to increase the number of classes in the hierarchy to see how it scales. Remember that you'll have an extra pointer in the visitor class per visitable class. – Alexandre C. Aug 04 '11 at 13:27
  • No this was in a simple console app. I'd generally avoid using dynamic_cast for shared libraries anyway because it isn't very robust. I expect it would be significantly slower.. at a guess, the difference in performance between the two methods would probably be less. – Pete Aug 04 '11 at 13:59