Интерфейсные классы для универсального применения шаблона посетителя

Я столкнулся с проблемой, которую можно было решить с помощью шаблона посетителя, и, поскольку я люблю многократно используемый код, я подумал, что было бы неплохо иметь несколько классов, которые уже делают большую часть магии для меня и которые я мог бы повторно использовать позже. Итак, я хотел что-то вроде класса Visitor и класса Visitable, из которого я мог бы подготовить производный класс для использования шаблона посетителя. Я придумал этот код:

template <typename ret = void>
class Visitor
{
public:
    typedef ret ReturnType;

protected:
    Visitor() {}
    ~Visitor() {}
};

template <typename BaseType>
class Visitable
{
public:
    template <typename Visitor>
    typename Visitor::ReturnType applyVisitor(Visitor& visitor)
    {
        return visitor(static_cast<BaseType*>(this));
    }

    template <typename Visitor>
    typename Visitor::ReturnType applyVisitor(Visitor& visitor) const
    {
        return visitor(static_cast<BaseType*>(this));
    }

protected:
    Visitable() {}
    ~Visitable() {}
};

template <typename VisitorType, typename VisitableType>
inline typename VisitorType::ReturnType applyVisitor(VisitorType visitor, VisitableType visitable)
{
    return visitable->applyVisitor(visitor);
}

class Base : public Visitable <Base>
{
public:
    virtual void foo() const
    {
        std::cout << "BASE" << std::endl;
    };

    std::string foobar() const
    {
        return "BASE";
    };
};

class Derived : public Base, public Visitable<Derived>
{
public:
    using Visitable<Derived>::applyVisitor;

    void foo() const
    {
        std::cout << "DERIVED" << std::endl;
    }; 

    std::string bar() const
    {
        return "DERIVED";
    };
};

struct MyVisitor : public Visitor < >
{
    template <class T>
    void operator()(T const var) const
    {
        var->foo();
    }
};

struct MyOtherVisitor : public Visitor <std::string>
{
    std::string operator()(Base * const var) const
    {
        return var->foobar();
    }

    std::string operator()(Derived * const var) const
    {
        return var->bar();
    }
};

int main(int _Argc, char* _Argv)
{
    Base *pVirtualDerived = new Derived();
    Base *pBase = new Base();
    Derived *pDerived = new Derived();

    std::cout << "Member method:" << std::endl;

    applyVisitor(MyVisitor(), pVirtualDerived);
    applyVisitor(MyVisitor(), pBase);
    applyVisitor(MyVisitor(), pDerived);

    std::cout << std::endl << "External method:" << std::endl;

    std::cout << applyVisitor(MyOtherVisitor(), pVirtualDerived) << std::endl;
    std::cout << applyVisitor(MyOtherVisitor(), pBase) << std::endl;
    std::cout << applyVisitor(MyOtherVisitor(), pDerived) << std::endl;
}

Как можно догадаться по названиям, меня вдохновил boost ::static_visitor и boost::variant. Однако можно также заметить, что моя реализация имеет недостатки в двух аспектах:

  1. Недостаточно просто наследоваться от Visitable, мне также нужно поместить объявление using в мой класс, чтобы устранить неоднозначность для метода applyVisitor.
  2. На самом деле это не шаблон посетителя. Вызов applyVisitor с Base*, который на самом деле указывает на объект Derived, вызывает не Derived::foo, а Base::foo. Я не могу объявить applyVisitor в Visitable<T> виртуальном, потому что это шаблонный метод. Но мне нужен шаблон, потому что Visitor<T> сам по себе является классом шаблона, и я хотел бы сохранить общий тип возвращаемого значения для моих посетителей.

Короче говоря, могу ли я каким-то образом решить обе проблемы и в итоге получить два класса, от которых мне просто нужно получить производные, чтобы подготовить мой код для шаблона посетителя?


person sigy    schedule 03.07.2014    source источник
comment
это должна быть ваша собственная реализация или вы можете использовать другие библиотеки? Библиотека Loki имеет общую реализацию для посетителя.   -  person MatthiasB    schedule 03.07.2014
comment
Я думал, может ли CRTP быть решением, и нашел это: shanhe.me/2011/08/06/ и stackoverflow.com/a/ 7877397/104774 Помогут ли они решить вашу проблему?   -  person stefaanv    schedule 03.07.2014
comment
Я уже использую здесь технику CRTP, однако пример кода из вашей ссылки поддерживает только фиксированные типы возврата, но я хочу поддерживать любой тип возврата. Другой пример, кажется, основан на реализации библиотеки Loki. Так что я посмотрю на это.   -  person sigy    schedule 03.07.2014
comment
Я здесь не из-за C++, а потому что это вопрос посетителя. Вот как это было сделано на Java (надеюсь, это будет полезно): musingsofaprogrammingaddict.blogspot.ca/2009/01/   -  person Fuhrmanator    schedule 04.07.2014
comment
@MatthiasB: К сожалению, похоже, что конструкции из библиотеки Loki не применимы к моему случаю. Они не могут справиться с наследованием.   -  person sigy    schedule 08.07.2014
comment
@stefaanv: другой пример не работает с наследованием, и, кроме того, посещаемые объекты должны знать возвращаемый тип своих посетителей.   -  person sigy    schedule 08.07.2014
comment
@Fuhrmanator: в этом примере посещаемые объекты должны знать обо всех посетителях, это не очень удобный код.   -  person sigy    schedule 08.07.2014
comment
«это не совсем поддерживаемый код» — почему вы так говорите? Предположение о проблеме посетителя состоит в том, что посетители — это то, что нужно поддерживать. Посещаемые объекты должны быть стабильными (это предположение шаблона). Посетитель что-то делает с объектами для посещения, поэтому он должен знать, что это такое. Можете ли вы привести реальный пример проблемы, когда посетитель не знает конкретно, что он посещает?   -  person Fuhrmanator    schedule 09.07.2014
comment
Нет, я говорил об обратном. В коде, который вы предоставили, посетитель должен знать обо всех конкретных посетителях. Поэтому каждый раз, когда мне нужен новый посетитель, мне нужно будет редактировать свои посещаемые объекты. В моем примере я хочу, чтобы посетители были в моей библиотеке, но посетители должны быть определены пользователем. Поэтому я ищу интерфейс, который позволяет всем посетителям получать доступ к моим объектам для посещения. И я не хочу трогать код моих Visitables, когда клиенту моей библиотеки нужен другой тип Visitor.   -  person sigy    schedule 09.07.2014


Ответы (1)


Можно много говорить о шаблонах проектирования посетителей и наследовании C++. В конкретном случае, который вы описали, я думаю, что решения следующие:

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

class Derived : public Base
{
public:
    void foo() const
    {
        std::cout << "DERIVED" << std::endl;
    };
};

Что касается второй проблемы, я думаю, вы просто забыли ключевое слово virtual в классе Base:

class Base : public Visitable<Base>
{
    public:
        virtual void foo() const
        {
            std::cout << "BASE" << std::endl;
        };
};
person Hugo Corrá    schedule 03.07.2014
comment
В моем примере кода это не отображалось, поэтому я обновил его соответствующим образом, но я думаю, что мне нужно, чтобы оба класса производились от Visitable, чтобы static_cast работал (почти), вероятно, в случае, если мне нужно вызывать разные методы в зависимости от фактического типа. По этой причине мой пример кода был немного глупым, потому что то, что я там делал, уже можно было сделать с помощью простого полиморфизма. - person sigy; 03.07.2014