Хорошо, вот моя проблема: у меня есть базовый композитный класс, который принимает посетителя, а затем перебирает его узлы. Работает как шарм.
Но затем мне пришлось использовать производную от этого композитного кода и признать, что я должен переопределить метод «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 ()», чтобы сделать его более точным …
Любое элегантное решение по этому поводу?
На самом деле, нет. Это то, что заставляет посетителей чувствовать себя немного больно. Хотя вы можете немного уменьшить дублирование с помощью шаблона:
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 ++. Так что, я полагаю, можно утверждать, что вышесказанное выглядит «естественно».
Если вы хотите, чтобы какой-то код выполнялся всегда, используйте шаблон не-виртуального интерфейса (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:
};