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

#c #templates #design-patterns #c 11 #visitor-pattern

#c #шаблоны #шаблоны проектирования #c 11 #посетитель-шаблон

Вопрос:

Я пытаюсь реализовать шаблон посетителя с помощью шаблонных производных классов

Я работаю с gcc 4.5

вот VisitorTemplate.hpp, я специализируюсь на классе Visitor, но я хотел бы иметь возможность обрабатывать любой тип:

редактировать: благодаря предложениям interjay код теперь компилируется и выполняется без ошибок

 #ifndef VISITORTEMPLATE_HPP_
#define VISITORTEMPLATE_HPP_

#include <iostream>
#include <string>
using namespace std;

template<class T> Derived;

class Visitor
{
  public:
    virtual void visit(Derived<string> *e) = 0;
};

class Base
{
  public:
    virtual void accept(class Visitor *v) = 0;
};

template<class T>
Derived: public Base
{
  public:
    virtual void accept(Visitor *v)
    {
       v->visit(this);
    }
    string display(T arg)
    {
       string s = "This is : "   to_string(arg);
       return s;
    }
};

class UpVisitor: public Visitor
{
   virtual void visit(Derived<string> *e)
   {
     cout << "do Up on "   e->display("test") << 'n';
   }
};

class DownVisitor: public Visitor
{
   virtual void visit(Derived<string> *e)
   {
     cout << "do Down on "   e->display("test") << 'n';
   }
};

#endif /* VISITORTEMPLATE_HPP_ */
  

main.cpp

 Base* base = new Derived<string>();
Visitor* up = new UpVisitor();
Visitor* down = new DownVisitor();
base->accept(up);
base->accept(down);
  

Теперь моя цель — использовать производный в visit без специализации; к сожалению, visit — это виртуальный метод, поэтому я не могу его шаблонировать

Комментарии:

1. Какой компилятор вы используете? Я только что отправил ваш пример кода в Visual C 2010, и он скомпилировался довольно удачно, если не считать жалобы на то, что std::to_string не может обрабатывать тип аргумента параметра шаблона. Я не вижу в вашем примере кода какой-либо причины, по которой он жаловался бы на неполный тип, поскольку вы не упустили возможность предоставления реализаций для единственной чисто виртуальной функции.

2. @MatthewWalton: «Я работаю с gcc 4.5». Однако всегда приятно указывать строки, на которые ссылаются сообщения компилятора. Я не хочу считать 31 строку в своем браузере.

3. @sbi doh, я, должно быть, забыл об этом, когда загружал код своему компилятору. Тем не менее, я бы не подумал, что этот конкретный тип ошибки относится к тому типу, по которому GCC и VC не согласятся.

4. хорошо, забудьте мой код, у вас есть идея справиться с этой общей проблемой Vistor?

5. Интересно. Кажется, что прямое объявление the class не совсем сочетается с template s

Ответ №1:

Из современного C — Разработка общего программирования и применение шаблонов проектирования — Андрей Александреску

 #include <iostream>

class BaseVisitor
{
    public:
        virtual ~BaseVisitor() {};
};

template <class T, typename R = int>
class Visitor
{
    public:
        virtual R visit(T amp;) = 0;
};

template <typename R = int>
class BaseVisitable
{
    public:
        typedef R ReturnType;
        virtual ~BaseVisitable() {};
        virtual ReturnType accept(BaseVisitor amp; )
        {
            return ReturnType(0);
        }
    protected:
        template <class T>
        static ReturnType acceptVisitor(T amp;visited, BaseVisitor amp;visitor)
        {
            if (Visitor<T> *p = dynamic_cast< Visitor<T> *> (amp;visitor))
            {
                return p->visit(visited);
            }
            return ReturnType(-1);
        }

        #define VISITABLE() 
            virtual ReturnType accept(BaseVisitor amp;v) 
                { return acceptVisitor(*this, v); }
};


/** example of use */
class Visitable1 : public BaseVisitable<int>
{
    /* Visitable accept one BaseVisitor */
    public:
        VISITABLE();
};

class Visitable2 : public BaseVisitable<int>
{
    /* Visitable accept one BaseVisitor */
    public:
        VISITABLE();
};

class VisitorDerived : public BaseVisitor,
        public Visitor<Visitable1, int>,
        public Visitor<Visitable2, int>
{
    public:
        int visit(Visitable1 amp; c)
        {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
        int visit(Visitable2 amp; c)
        {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
};

int main(int argc, char **argv)
{
    VisitorDerived visitor;
    Visitable1 visitable1;
    Visitable2 visitable2;

    visitable1.accept(visitor);
    visitable2.accept(visitor);
}
  

Можно ли избежать dynamic_cast с помощью шаблона CRTP, например:

 #include <iostream>

class BaseVisitor
{
    public:
        virtual ~BaseVisitor() {};
};

template <class T>
class Visitor
{
    public:
        virtual void visit(T amp;) = 0;
};

template <class Visitable>
class BaseVisitable
{ 
    public:
        template <typename T>
        void accept(T amp; visitor)
        {
            visitor.visit(static_cast<Visitable amp;>(*this));
        }
};

/** example of use */
class Visitable1 : public BaseVisitable<Visitable1>
{
};

class Visitable2 : public BaseVisitable<Visitable2>
{
};

class VisitorDerived : public BaseVisitor, 
                       public Visitor<Visitable1>,
                       public Visitor<Visitable2>
{
    public:
        void visit(Visitable1 amp; c)
        {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
        void visit(Visitable2 amp; c)
        {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
        }
};

int main(int argc, char **argv)
{
    VisitorDerived visitor;
    Visitable1 visitable1;
    Visitable2 visitable2;

    visitable1.accept<VisitorDerived>(visitor);
    visitable2.accept<VisitorDerived>(visitor);
}
  

Комментарии:

1. Я нахожу это немного сложным; можно ли избежать dynamic_cast ?

2. Да, вы можете использовать шаблон CRTP, например:

3. Как бы вы расширили это, чтобы охватить иерархии, например, DerivedVisitable1, сохраняя при этом static_cast ? Возможно ли это без перехода к циклической реализации?

4. Как можно изменить этот код, чтобы разрешить контейнер базовых посещений. Используя первый пример, я мог бы иметь «std::vector<BaseVisitable>», но я не могу для второго, поскольку BaseVisitable является шаблоном.

Ответ №2:

Ваш Derived класс не может использовать Visitor , потому что он еще не определен (он был объявлен только вперед и, следовательно, является неполным типом).

Вы можете исправить ошибку компиляции, поместив Visitor определение перед Derived . Вам также потребуется переадресовать объявление Derived перед определением Visitor :

 template <class T> class Derived;

class Visitor {
public:
    virtual void visit(Derived<string> *e) = 0;
};

template <class T>
class Derived : public Base {
    //.... can call Visitor methods here ...
};