Комментарии 14
Ну и тогда проблемы, которая решается в статье, нет вообще.
У obj нет метода visit. Если бы был, тогда нужна перегрузка для всех типов — это циклическая зависимость.
Про этот пример можно подробно прочитать в книге Andrei Alexandrescu. Modern c++ design.
В вашем примере в итоге весь выигрыш из-за неудачной реализации dynamic_cast, который вы с помощью 3х листов исходного кода, с блекджеком и шаблонами реализуете руками с помощью map<>.
На мой взгляд, не нужно множить сущности, http://ideone.com/K0KQhk вот реализация проще, расширяемая и более эффективная, т.к switch() отлично оптимизируется компилятором
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
enum e_type {
ENTITY,
GEOMETRY,
MODEL
};
struct visitor_base;
struct visitable
{
virtual void accept(visitor_base & obj) = 0;
};
struct visitor_base
{
virtual void visit(visitable &, enum e_type type) = 0;
};
struct entity : visitable
{
void accept(visitor_base & obj)
{
obj.visit(*this, ENTITY);
}
};
struct geometry : entity
{
void accept(visitor_base & obj)
{
obj.visit(*this, GEOMETRY);
}
};
struct model : geometry
{
void accept(visitor_base & obj)
{
obj.visit(*this, MODEL);
}
};
struct test_visitor : visitor_base
{
void visit(visitable & obj, enum e_type type)
{
switch(type)
{
case ENTITY:
{
entity &ent = (entity&)obj; // its enough safe
cout << "Visited ENTITY" << endl;
break;
}
case GEOMETRY:
{
geometry &geo = (geometry&)obj;
cout << "Visited GEOMETRY" << endl;
break;
}
case MODEL:
{
model &mod = (model&)obj;
cout << "Visited MODEL" << endl;
break;
}
default:
// TRAP
;
}
}
};
struct modern_visitor : test_visitor // extension for new objects
{
// add new methods for new visitables, call parent otherwise
};
int main() {
entity E;
geometry G;
model M;
vector<entity*> vec = {&E, &G, &M};
test_visitor visi;
for_each(vec.begin(), vec.end(), [&](entity *i){ i->accept(visi); });
// your code goes here
return 0;
}
Но как пишет Александреску — это самый неудачный паттерн, потому редко используемый
Это немного упрошенный пример, у Александреску более аккуратный, обобщенный шаблон (в Loki, dynamic_cast вынемен отдельно), но суть там такая же. Не такой уж он и неудачный.
Намой взгляд, возможности по расшерению и поддержке этой модели, такие же как и у Cyclic Visitor. Только вместо виртуальной функции мне нужно добавить поле в enum, вы же понимаете, что если я добавлю поле в enum все существующие visitor'ы нужно будет перекомпелировать. Что если это библиотека и у меня нет возможности добавить поле в enum, также как у меня может не быть возможности добавить виктуальную функцию в случаи с Cyclic Visitor и перекомпилировать все?
#include <iostream>
template <class T>
struct visitor {
virtual void visit( T& ) = 0;
};
template <class T, class... Base>
struct visitable : Base... {
template <class Visitor>
void accept( Visitor& obj )
{
static_cast<visitor<T>&>( obj ).visit( static_cast<T&>( *this ) );
}
};
struct entity : visitable<entity> {
};
struct geometry : visitable<geometry, entity> {
};
struct model : visitable<model, geometry> {
};
struct test_visitor : visitor<entity>, visitor<geometry>, visitor<model> {
void visit( entity& obj ) override
{
std::cout << "entity\n";
}
void visit( geometry& obj ) override
{
std::cout << "geometry\n";
}
void visit( model& obj ) override
{
std::cout << "model\n";
}
};
int main()
{
entity e;
geometry g;
model m;
test_visitor v;
e.accept( v );
g.accept( v );
m.accept( v );
}
#include <iostream>
#include <vector>
template <class T>
struct visitor_base {
template <class R>
void visit_base( R& obj )
{
static_cast<typename R::visitor_type&>( *this ).visit( obj );
}
};
template <class T>
struct visitor : T::parent_visitor_type {
virtual void visit( T& ) = 0;
};
template <class T, class Base = void>
struct visitable : Base {
using parent_visitor_type = typename Base::visitor_type;
using visitor_type = visitor<T>;
void accept( typename Base::visitor_base_type& obj ) override
{
obj.visit_base( static_cast<T&>( *this ) );
}
};
template <class T>
struct visitable<T, void> {
using parent_visitor_type = visitor_base<T>;
using visitor_base_type = visitor<T>;
using visitor_type = visitor_base_type;
virtual void accept( visitor_base_type& obj )
{
obj.visit( static_cast<T&>( *this ) );
}
};
struct entity : visitable<entity> {
};
struct geometry : visitable<geometry, entity> {
};
struct geometry2 : geometry {
};
struct model : visitable<model, geometry2> {
};
struct test_visitor : visitor<model> {
void visit( entity& obj ) override
{
std::cout << "entity\n";
}
void visit( geometry& obj ) override
{
std::cout << "geometry\n";
}
void visit( model& obj ) override
{
std::cout << "model\n";
}
};
int main()
{
entity e;
geometry g;
geometry2 g2;
model m;
test_visitor v;
e.accept( v );
g.accept( v );
g2.accept( v );
m.accept( v );
std::vector<entity*> vec{&e, &g, &g2, &m};
for ( auto it : vec ) {
it->accept( v );
}
}
А если у меня иерархия не линейная?
struct entity {};
struct geometry : entity {};
struct model : entity {};
#include <iostream>
#include <type_traits>
#include <vector>
template <class T>
struct id {
static id id_;
};
template <class T>
id<T> id<T>::id_;
template <class T>
struct visitor_base {
using common_type = T;
virtual void visit_base( T& obj, void* ptr ) = 0;
};
template <class T, class Base = void>
struct visitable : Base {
void accept( typename Base::visitor_type& obj ) override
{
obj.visit_base( static_cast<T&>( *this ), &id<T>::id_ );
}
};
template <class T>
struct visitable<T, void> {
using visitor_type = visitor_base<T>;
virtual void accept( visitor_type& obj )
{
obj.visit_base( static_cast<T&>( *this ), &id<T>::id_ );
}
};
template <class...>
struct tag {
};
template <class T>
struct visitor_impl {
virtual void visit( T& ) = 0;
};
template <class... Ts>
struct visitor : visitor_impl<Ts>..., std::common_type<Ts...>::type::visitor_type {
using common_type = typename std::common_type<Ts...>::type::visitor_type::common_type;
void visit_base( common_type& obj, void* ptr ) override
{
visit_( obj, ptr, tag<Ts...>() );
}
private:
void visit_( common_type& obj, void*, tag<> )
{
}
template <class Head, class... Tail>
void visit_( common_type& obj, void* ptr, tag<Head, Tail...> )
{
if ( ptr == &id<Head>::id_ ) {
static_cast<visitor_impl<Head>&>( *this ).visit( static_cast<Head&>( obj ) );
}
else {
visit_( obj, ptr, tag<Tail...>() );
}
}
};
struct entity : visitable<entity> {
};
struct geometry : visitable<geometry, entity> {
};
struct model : visitable<model, entity> {
};
struct test_visitor : visitor<entity, geometry, model> {
void visit( entity& ) override
{
std::cout << "entity\n";
}
void visit( geometry& ) override
{
std::cout << "geometry\n";
}
void visit( model& ) override
{
std::cout << "model\n";
}
};
int main()
{
entity e;
geometry g;
model m;
test_visitor v;
e.accept( v );
g.accept( v );
m.accept( v );
std::cout << "\n";
std::vector<entity*> vec{&e, &g, &m};
for ( auto it : vec ) {
it->accept( v );
}
}
Только вот ему пока не хватает `constexpr` hash функции.
Acyclic Visitor