Вызов более конкретной перегруженной функции из ссылки на базовый класс
Прежде всего, извините за загадочный заголовок, это нелегко объяснить.
Я хочу реализовать шаблон Visitor в C++, используя перегруженные функции. Это моя ситуация:
- У меня есть коллекция парсеров. Каждый синтаксический анализатор возвращает определенный производный тип
Element
, По сути, я получаю полиморфную коллекциюElement
с, все они реализуютvisit(Visitor&)
функция. - У меня есть несколько анализаторов (
Visitor
с). Каждый посетитель интересуется только несколькими конкретнымиElement
производные классы. - Базовый класс для
Visitor
имеет пустую реализацию дляvisit(Element&)
который получает в качестве аргументаElement
ссылка. - каждый
Visitor
производный класс реализуетvisit
функции для конкретных типов элементов, которые его интересуют.visit(DerivedElement&)
перегруженная функция вDerivedVisitor
учебный класс. - При звонке
accept(Visitor& v) { v.visit(*this); }
вызываемая функция должна быть более конкретной. То есть еслиv
этоDerivedVisitor
а такжеaccept
реализуется вDerivedElement
Я хотел бы функциюvisit(DerivedElement&)
быть названным.
Пример кода:
#include <iostream>
using namespace std;
class Visitor
{
public:
virtual void visit(class BaseElement& e);
};
class BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on BaseElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on BaseElement" << endl;
}
};
void Visitor::visit(BaseElement& e)
{
cout << "visit on Visitor" << endl;
e.doThings();
}
class DerivedElement : public BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on DerivedElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on DerivedElement" << endl;
}
};
class DerivedVisitor : public Visitor
{
public:
void visit(BaseElement& e)
{
cout << "visit-BaseElement on DerivedVisitor" << endl;
e.doThings();
}
void visit(DerivedElement &e)
{
cout << "visit-DerivedElement on DerivedVisitor" << endl;
e.doThings();
}
};
int main(int argc, char** argv)
{
BaseElement eBase;
DerivedElement eDeriv;
BaseElement& eDerivAsBase = eDeriv;
Visitor vBase;
DerivedVisitor vDeriv;
cout << "Visiting a BaseElement with the base visitor:" << endl;
eBase.accept(vBase);
cout << endl << "Visiting a BaseElement with the derived visitor:" << endl;
eBase.accept(vDeriv);
cout << endl << "Visiting Base and Derived elements with the derived visitor" << endl;
eBase.accept(vDeriv);
eDeriv.accept(vDeriv);
cout << endl << "Visiting Base element as Derived reference" << endl;
eDerivAsBase.accept(vBase);
eDerivAsBase.accept(vDeriv);
}
Это выход
Visiting a BaseElement with the base visitor:
accept on BaseElement
visit on Visitor
doThings on BaseElement
Visiting a BaseElement with the derived visitor:
accept on BaseElement
visit-BaseElement on DerivedVisitor
doThings on BaseElement
Visiting Base and Derived elements with the derived visitor
accept on BaseElement
visit-BaseElement on DerivedVisitor
doThings on BaseElement
accept on DerivedElement
visit-BaseElement on DerivedVisitor (!)
doThings on DerivedElement
Visiting Base element as Derived reference
accept on DerivedElement
visit on Visitor
doThings on DerivedElement
accept on DerivedElement
visit-BaseElement on DerivedVisitor (!)
doThings on DerivedElement
Строки, отмеченные (!), - это те, которые я хочу изменить. Эти строки должны быть "визит-DerivedElement на DerivedVisitor".
Это возможно? Видя, что C++ не реализует множественную диспетчеризацию, это кажется трудным, и я, вероятно, требую невозможного Тем не менее, я бы очень хотел посмотреть, какие альтернативы у меня есть, как писать пустые accept(DerivedElementN&)
методы для каждого из производных элементов, которые у меня есть, не кажутся лучшим вариантом.
2 ответа
Вы делаете большую динамическую косвенность здесь. Таким образом, вам нужно будет построить своих посетителей таким же образом.
struct BaseVisitor {
std::unordered_map<std::type_info, std::function<void(BaseElement&)>> types;
template<typename D, typename F> void AddOverload(F f) {
types[typeid(D)] = [=](BaseElement& elem) {
f(static_cast<D&>(elem));
};
}
virtual void visit(BaseElement& elem) {
if (types.find(typeid(elem)) != types.end())
types[typeid(elem)](elem);
}
};
struct DerivedVisitor : BaseVisitor {
DerivedVisitor() {
AddOverload<DerivedElement>([](DerivedElement& e) {
});
//... etc
}
};
Основная проблема заключается в том, что вы не можете использовать шаблоны, если вам нужна эта динамическая косвенность. Все, что вы можете сделать, это предложить дополнительный уровень безопасности типов и удобства (и, возможно, скорости) по сравнению со спамом dynamic_cast
,
В качестве краткого замечания, приведенный выше код может работать не совсем корректно - есть забавное использование typeid для ссылок или const
или что-то в этом роде, которое может привести к сбою при поиске типов, когда они должны преуспеть.
Существуют и другие методы, которые могут устранить это ограничение, если это важно для вас, но вы можете придерживаться dynamic_cast, потому что это весело, но ужасно.
Предложение
Измените заголовок вашего сообщения на "Как реализовать специализацию с помощью шаблона посетителя".
Примечание: вы забыли использовать virtual
Ключевое слово в DerivedVisitor
учебный класс.
Ответ
Вы используете 2 функции, которые плохо сочетаются:
Переопределение метода ("виртуальный"). В котором методы из связанных классов и с одинаковым идентификатором имеют одинаковые параметры.
class Visitor
{
public:
virtual void visit(BaseElement& e)
{
// print element value to console
}
};
class DerivedVisitor : public Visitor
{
public:
virtual void visit(BaseElement& e)
{
// save element value into file
}
};
Перегрузка метода. В котором методы методов из связанных классов и с одинаковым идентификатором имеют разные параметры.
class Visitor
{
public:
void visit(BaseElement& e)
{
// ...
}
};
class DerivedVisitor : public Visitor
{
public:
void visit(BaseElement& e)
{
// ...
}
void visit(DerivedElement& e)
{
// ...
}
};
Поскольку вы не показываете достаточно исходного кода, я предполагаю, что вы передаете параметр как Element&
, даже если он был создан как DerivedElement&
поэтому компилятор вызывает void visit(BaseElement& e)
, вместо void visit(DerivedElement& e)
,
Также отмечу, что вы используете "ссылки", а не указатели. Возможно, что компилятор использует DerivedElement
в BaseElement
, Я предлагаю вместо этого использовать "указатели".
При использовании полиморфизма лучше не использовать "Перегрузку метода", а придерживаться "Переопределения метода", поскольку компилятор всегда вызывает "виртуальный" или "переопределенный" метод.
class BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on BaseElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on BaseElement" << endl;
}
};
class FooElement: BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on FooElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on FooElement" << endl;
}
};
class BarElement: BaseElement
{
public:
virtual void accept(Visitor &v)
{
cout << "accept on BarElement" << endl;
v.visit(*this);
}
virtual void doThings()
{
cout << "doThings on BarElement" << endl;
}
};
class Visitor
{
public:
void visit(BaseElement* e)
{
cout << "visit Any Element on Visitor" << endl;
e.doThings();
}
};
class DerivedVisitor : public Visitor
{
public:
void visit(BaseElement* e)
{
cout << "visit Any Element on DerivedVisitor" << endl;
e.doThings();
}
};
int main (...)
{
BaseElement* e1 = new FooElement();
BaseElement* e2 = new BarElement();
BaseVisitor* v = new DerivedVisitor();
v->visit(e1);
v->visit(e2);
free v();
free e2();
free e1();
} // int main (...)
Примечание: я пропускаю большую часть операции посетителя, просто чтобы упростить операцию перегрузки.
ОБНОВЛЕНИЕ: С переопределением метода (AKA "виртуальное" ключевое слово), специализация на каждом Element
код класса, а не на Visitor
код класса. Соответствующий метод вызывается из каждого производного класса.
Приветствия.