Вызов более конкретной перегруженной функции из ссылки на базовый класс

Прежде всего, извините за загадочный заголовок, это нелегко объяснить.

Я хочу реализовать шаблон Visitor в C++, используя перегруженные функции. Это моя ситуация:

  1. У меня есть коллекция парсеров. Каждый синтаксический анализатор возвращает определенный производный тип Element, По сути, я получаю полиморфную коллекцию Elementс, все они реализуют visit(Visitor&) функция.
  2. У меня есть несколько анализаторов (Visitorс). Каждый посетитель интересуется только несколькими конкретными Element производные классы.
  3. Базовый класс для Visitor имеет пустую реализацию для visit(Element&) который получает в качестве аргумента Element ссылка.
  4. каждый Visitor производный класс реализует visit функции для конкретных типов элементов, которые его интересуют. visit(DerivedElement&) перегруженная функция в DerivedVisitor учебный класс.
  5. При звонке 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 код класса. Соответствующий метод вызывается из каждого производного класса.

Приветствия.

Другие вопросы по тегам