4

Ok: here´s my problem: I have a base composit class which accepts a visitor an then iterates over its nodes. Works like a charm. But then, I have to used a derived from this composit and recognized that I have to override the "accept()" method in the derived class to have correct double dispatching (which I did not understand).

This brings out two flaws: first, I have to break the hidden structure of the base, and second, I have to duplicate code. To clearify, here´s my pseudo-code:

struct Visitor
{
    void visit( BaseComposit*)    { throw( "not expected"); };
    void visit( DerivedComposit*) { throw( "ok"); };
};

class BaseComposit 
{
private:  

    std::vector< BaseComposit*> nodes;

public:

    virtual void accept( Visitor* v)
    {
        v->visit( this);

        for( int i = 0; i < nodes.size(); i++)
            nodes[ i]->accept( v);
    }
};

class DerivedComposit : public BaseComposit
{
public:
};

Any ellegant solution on that ? Thank you !

Edit: added "virtual" to "accept()" to make it more precise...

2 Answers2

1

Any ellegant solution on that ?

Not really. That's what makes the visitor pattern a bit of a pain. Though you could mitigate the duplication a bit with the help of a template:

class BaseComposit 
{
private:  

    std::vector<BaseComposit*> nodes;

protected:

    template<class T>
    void accept_impl( Visitor* v, T* this_ )
    {
        v->visit( this_ );

        for(int i = 0; i < nodes.size(); i++)
            nodes[i]->accept(v);
    }

public:

    virtual void accept( Visitor* v ) { accept_impl( v, this ); }
};

Now the duplication that accept must incur is smaller.

Also, as @Oliv pointed out, your example really ought to have accept be a virtual function. Otherwise the whole thing won't work.


If you feel really adventurous, you can introduce a macro to ease the "injection" of accept into each class. Like so:

#define INJECT_ACCEPT() void accept( Visitor* v ) override { accept_impl( v, this ); }

But you'd still need to use it in each derived class to make the definition appear.

class DerivedComposit : public BaseComposit
{
public:
    INJECT_ACCEPT();
};

The semi-colon is allowed by a curious feature of the C++ grammar. So I suppose one may argue the above looks "natural".

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
1

If you want to force some code to be always executed, use the non-virtual interface (NVI) pattern:

class BaseComposit 
{
private:  
    std::vector<BaseComposit*> nodes;

    virtual void call_visit(Visitor* v) { v->visit(this); }

public:

    void accept(Visitor* v)
    {
        call_visit(v);

        for(int i = 0; i < nodes.size(); i++)
            nodes[i]->accept(v);
    }
};

class DerivedComposit : public BaseComposit
{
    void call_visit(Visitor* v) override { v->visit(this); }
public:
};
Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
  • @affenärschle: so get rid of `override`. – Jarod42 Dec 07 '17 at 10:07
  • @Jarod42: jup - works. But I can´t see why the compiler can select the right vtable entry of Base/Derived and not in the visitor ... Anyway, Sebastian, good solution. Ok - visitor has no vtabel ... shame on me – affenärschle Dec 07 '17 at 10:20