#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 ...
};