Посетитель и двойная отправка без переопределения & quot; принять & quot; метод в переполнении стека

Хорошо, вот моя проблема: у меня есть базовый композитный класс, который принимает посетителя, а затем перебирает его узлы. Работает как шарм.
Но затем мне пришлось использовать производную от этого композитного кода и признать, что я должен переопределить метод «accept ()» в производном классе, чтобы иметь правильную двойную диспетчеризацию (которую я не понял).

Это выявляет два недостатка: во-первых, мне приходится ломать скрытую структуру базы, а во-вторых, мне приходится дублировать код. Для ясности вот мой псевдокод:

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:
};

Любое элегантное решение по этому поводу?
Спасибо !

Редактировать: добавил «виртуальный» в «accept ()», чтобы сделать его более точным …

2

Решение

Любое элегантное решение по этому поводу?

На самом деле, нет. Это то, что заставляет посетителей чувствовать себя немного больно. Хотя вы можете немного уменьшить дублирование с помощью шаблона:

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 ); }
};

Теперь дублирование accept должен понести меньше.

Кроме того, как отметил @Oliv, ваш пример действительно должен иметь accept быть виртуальной функцией. В противном случае все это не сработает.


Если вы действительно любите приключения, вы можете ввести макрос, чтобы облегчить «инъекцию» accept в каждый класс. Вот так:

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

Но вам все равно нужно использовать его в каждом производном классе, чтобы появилось определение.

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

Точка с запятой допускается любопытной особенностью грамматики C ++. Так что, я полагаю, можно утверждать, что вышесказанное выглядит «естественно».

1

Другие решения

Если вы хотите, чтобы какой-то код выполнялся всегда, используйте шаблон не-виртуального интерфейса (NVI):

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:
};
1

По вопросам рекламы [email protected]