[C++] Обобщаем паттерн посетитель (С++)
Автор
Сообщение
news_bot ®
Стаж: 7 лет 2 месяца
Сообщений: 27286
Недостатки типичной реализацииВ статье намеренно не приведен пример типичной реализации паттерна посетителя в C++.Если вы не знакомы с этим шаблоном, то вот тутможно с ним ознакомиться. А если в двух словах, то этот шаблон очень полезен если вам нужно обойти коллекцию из указателей на абстрактный базовый класс, применив к ним какую то операцию в зависимости от типа, который скрывается за абстракцией. Поэтому перейдем сразу к недостаткам, которые хотели бы устранить.
- При добавлении класса в иерархию (или удалении), приходится изменить много мест в коде. (Каждый раз лезть и добавлять чисто виртуальную функцию-член в абстрактный класс, и почти во все реализации.)
- При добавлении нового посетителя, приходится наследоваться от абстрактного класса, даже если мы всего то хотим вызвать просто шаблон функции. При этом мы не можем сделать шаблон виртуальной функции-члена visit у посетителя (причина этого проста, шаблонов виртуальных функций-членов просто нет). Каждую реализацию функции-члена visit приходится делать отдельно.
- Класс посетителя привязан к предметной области.
Что хотим получить?
- Имея на руках указатель на абстрактный базовый класс, хотим сразу получать реальный тип объекта и отправлять его самого или его тип в какой-нибудь шаблон функции, не используя при этом никаких конструкций из dynamic_cast или static_cast и не создавая множество одинаковых переопределений виртуальных функций.
- Простым способом добавлять или удалять классы, которые посетитель может обойти.
- Не привязанный к предметной области посетитель.
РеализацияНачинаем с создания абстрактного посетителя.Source:
template< class T >
struct AbstractVisitor
{
virtual ~AbstractVisitor() = default;
virtual void visit( T& ) = 0;
};
(Пояснение: здесь виртуальная функция-член visit не является шаблоном, количество виртуальных функций при инстанцировании класса AbstractVisitor точно известно т.к. T является параметром шаблона класса, а не функции )Инстанс конкретного посетителя сможет обойти только один тип объекта. Нам же необходимо чтобы посетитель мог обходить несколько различных типов объектов.Для этого создадим простой список типов TypeList и класс агрегатор AbstractVisitors. Список у AbstractVisitors будет содержать все типы объектов, которые посетитель может обойти.Source:
template< class ... T >
struct TypeList
{
};
template< class T >
struct AbstractVisitor
{
virtual ~AbstractVisitor() = default;
virtual void visit( T& ) = 0;
};
template< class ...T >
struct AbstractVisitors;
template< class ... T >
struct AbstractVisitors< TypeList< T... > > : AbstractVisitor< T >...
{
};
Т.к. мы не хотим каждый раз наследоваться от абстрактного посетителя, наследуемся от него один раз и будем принимать функтор (если быть точным, то принимать будем обобщённую лямбду). Для этого создадим класс Dispatcher.Source:
template< class Functor, class ... T >
struct Dispatcher;
template< class Functor, class ... T >
struct Dispatcher< Functor, TypeList< T... > > : AbstractVisitors< TypeList< T... > >
{
Dispatcher( Functor functor ) : functor( functor ) {}
Functor functor;
};
Теперь необходимо для всех типов из листа переопределить виртуальную функцию-член visit.Для этого создадим класс Resolver, который этим и будет заниматься. А сам класс Dispatcher унаследуем от всех возможных типов Resolver-ов. Дополнительно необходимо вызывать функтор в переопределенной функции, воспользуемся (CRTP) и передадим тип Dispatcher как аргумент шаблона во все Resolver.(Подробнее о том что такое CRTP можно почитать тут).Source:
template< class Dispatcher, class T >
struct Resolver : AbstractVisitor< T >
{
void visit( T& obj ) override
{
static_cast< Dispatcher* >( this )->functor( obj );
};
};
template< class Functor, class ... T >
struct Dispatcher< Functor, TypeList< T... > > : AbstractVisitors< TypeList< T... > >, Resolver< Dispatcher< Functor, TypeList< T... > >, T >...
{
Dispatcher( Functor functor ) : functor( functor ) {}
Functor functor;
};
Вроде все в порядке. Попробуем создать объект класс Dispatcher. Компилятор начинает ругаться на то, что объект класса Dispatcher абстрактный, как же так? Причина этого в том, что мы переопределили виртуальные функции для Resolver, но для Dispatcher мы ведь ничего не переопределяли. Чтобы этого избежать, необходимо сделать наследование от AbstractVisitor< T >виртуальным.(Подробнее о размещении объектов в памяти и виртуальном наследовании можно почитать тут.)Source:
template< class ... T >
struct AbstractVisitors< TypeList< T... > > : virtual AbstractVisitor< T >...
{
};
template< class Dispatcher, class T >
struct Resolver : virtual AbstractVisitor< T >
{
void visit( T& obj ) override
{
static_cast< Dispatcher* >( this )->functor( obj );
};
};
Создадим абстрактный базовый класс (AbstractObject) и какие-нибудь классы (Object1, Object2), которые хотели обойти. Так же создадим функцию test и шаблон функции test, которые будут получать непосредственно ссылку на объект определенного типа. Пример использования:Source:
struct Object1;
struct Object2;
using ObjectList = TypeList< Object1, Object2 >;
struct AbstractObject
{
virtual void accept( AbstractVisitors< ObjectList >& visitor ) = 0;
};
struct Object1 : AbstractObject
{
void accept( AbstractVisitors< ObjectList >& visitor ) override
{
static_cast< AbstractVisitor< Object1 >& >( visitor ).visit( *this );
};
};
struct Object2 : AbstractObject
{
void accept( AbstractVisitors< ObjectList >& visitor ) override
{
static_cast< AbstractVisitor< Object2 >& >( visitor ).visit( *this );
};
};
void test( Object1& obj )
{
std::cout << "1" << std::endl;
}
template< class T >
void test( T& obj )
{
std::cout << "2" << std::endl;
}
int main()
{
Object1 t1,t2,t3,t4;
Object2 e1,e2,e3;
std::vector< AbstractObject* > vector = { &t1, &e1, &t2, &t3, &e2, &e3, &t4 };
auto l = []( auto& obj ){ test(obj); };
Dispatcher<decltype(l), ObjectList> dispatcher;
for( auto* obj : vector )
{
obj->accept( dispatcher );
}
}
(Пояснение: мы не можем просто написать visitor.visit( *this ), это приведет к неоднозначности, если классов в иерархии будет больше двух.)Строчки на которых создается обобщенная лямбда и объект класса Dispatchable какие-то то страшные и не удобные, спрятать бы все это от глаз.Так же, хотелось бы спрятать функцию-член accept у AbstractObject, Object1 и Object2, т.к. тело функции для всех типов объектов будет одинаковое, различаться будет только тип объекта.Для этого создадим абстрактный класс Dispatchable. Cделаем у него чисто виртуальную функцию-член accept и шаблон функции-члена который будет принимать функтор. В нем собственно и будем создавать наш Dispatcher.Помимо этого создадим макрос DISPATCHED, он понадобится чтобы спрятать переопределение функции-члена accept у Object1 и Object2.Source:
template< class TypeList >
struct Dispatchable
{
virtual ~Dispatchable() = default;
virtual void accept( AbstractVisitors< TypeList >& ) = 0;
template< class Functor >
void dispatch( Functor functor )
{
static Dispatcher< decltype(functor), TypeList > dispatcher( functor );
accept( dispatcher );
};
};
#define DISPATCHED( TYPE, TYPE_LIST ) \
void accept( AbstractVisitors< TYPE_LIST >& visitor ) override \
{ \
static_cast< AbstractVisitor< TYPE >& >( visitor ).visit( *this ); \
}
Затем наследуем AbstractObject от класса Dispatchable. А в классы Object1 и Object2 добавляем макрос DISPATCHED.Source:
struct Object1;
struct Object2;
using ObjectList = TypeList< Object1, Object2 >;
struct AbstractObject : Dispatchable< ObjectList >
{
};
struct Object1 : AbstractObject
{
DISPATCHED( Object1, ObjectList )
};
struct Object2 : AbstractObject
{
DISPATCHED( Object2, ObjectList )
};
Отлично, мы спрятали все функции-члены accept и вынесли общий код. Вот теперь все готово. Пример использования:Source:
void test( Object1& obj )
{
std::cout << "1" << std::endl;
}
template< class T >
void test( T& obj )
{
std::cout << "2" << std::endl;
}
int main()
{
Object1 t1,t2,t3,t4;
Object2 e1,e2,e3;
std::vector< AbstractObject* > vector = { &t1, &e1, &t2, &t3, &e2, &e3, &t4 };
for( auto* obj : vector )
{
obj->dispatch( []( auto& obj ) { test(obj); } );
}
}
Output:1211221Заключение
- Чтобы добавить или удалить класс, который будет обрабатываться посетителем, достаточно просто изменить список типов.
- Посетитель не привязан к предметной области, т.к. является шаблоном класса.
- Можем в полной мере пользоваться шаблонами функций.
Какие недостатки?
- Дополнительная косвенность, т.к. Dispatcher содержит функтор.
Ссылкана код в compiler explorer.Full source:
#include <type_traits>
#include <iostream>
#include <vector>
template< class ... T >
struct TypeList
{
};
template< class T >
struct AbstractVisitor
{
virtual ~AbstractVisitor() = default;
virtual void visit( T& ) = 0;
};
template< class ...T >
struct AbstractVisitors;
template< class ... T >
struct AbstractVisitors< TypeList< T... > > : virtual AbstractVisitor< T >...
{
};
template< class Dispatcher, class T >
struct Resolver : virtual AbstractVisitor< T >
{
void visit( T& obj ) override
{
static_cast< Dispatcher* >( this )->functor( obj );
};
};
template< class Functor, class ... T >
struct Dispatcher;
template< class Functor, class ... T >
struct Dispatcher< Functor, TypeList< T... > > : AbstractVisitors< TypeList< T... > >, Resolver< Dispatcher< Functor, TypeList< T... > >, T >...
{
Dispatcher( Functor functor ) : functor( functor ) {}
Functor functor;
};
template< class TypeList >
struct Dispatchable
{
virtual ~Dispatchable() = default;
virtual void accept( AbstractVisitors< TypeList >& ) = 0;
template< class Functor >
void dispatch( Functor functor )
{
static Dispatcher< decltype(functor), TypeList > dispatcher( functor );
accept( dispatcher );
};
};
#define DISPATCHED( TYPE, TYPE_LIST ) \
void accept( AbstractVisitors< TYPE_LIST >& visitor ) override \
{ \
static_cast< AbstractVisitor< TYPE >& >( visitor ).visit( *this ); \
}
struct Object1;
struct Object2;
using ObjectList = TypeList< Object1, Object2 >;
struct AbstractObject : Dispatchable< ObjectList >
{
};
struct Object1 : AbstractObject
{
DISPATCHED( Object1, ObjectList )
};
struct Object2 : AbstractObject
{
DISPATCHED( Object2, ObjectList )
};
void test( Object1& obj )
{
std::cout << "1" << std::endl;
}
template< class T >
void test( T& obj )
{
std::cout << "2" << std::endl;
}
int main()
{
Object1 t1,t2,t3,t4;
Object2 e1,e2,e3;
std::vector< AbstractObject* > vector = { &t1, &e1, &t2, &t3, &e2, &e3, &t4 };
for( auto* obj : vector )
{
obj->dispatch( []( auto& obj ) { test(obj); } );
}
}
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Разработка игр] Реализация паттерна проектирования (перевод)
- [Программирование, C++] Введение в регулярные выражения в современном C++ (перевод)
- [Программирование, C++, Компиляторы, IT-стандарты] С++23: международный стандарт на удалёнке
- [Программирование, C++, C] Финальный релиз этого года — CLion 2020.3! С новыми функциями в отладчике, проверками MISRA и улучшениями для Qt
- [Open source, Программирование, C++] Не хочется ждать в очереди? Напишем свой диспетчер для SObjectizer с приоритетной доставкой
- [Разработка под iOS, Разработка мобильных приложений] Адаптируем UITableView под MVVM
- [C++, Программирование микроконтроллеров, Схемотехника, Производство и разработка электроники, DIY или Сделай сам] ESP32 Custom Board Mini
- [Программирование, C++, Работа с 3D-графикой, Разработка игр, CGI (графика)] Vulkan. Руководство разработчика. Рисуем треугольник (перевод)
- [Программирование, C++] Небольшие, но важные функции (перевод)
- [JavaScript, Node.JS, Яндекс API, Голосовые интерфейсы] Салют от Сбера в Яндекс.Облаке
Теги для поиска: #_c++, #_c++, #_pattern, #_visitor, #_crtp, #_templates, #_generics, #_lambda_functions, #_c++
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 27-Апр 11:41
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 7 лет 2 месяца |
|
Недостатки типичной реализацииВ статье намеренно не приведен пример типичной реализации паттерна посетителя в C++.Если вы не знакомы с этим шаблоном, то вот тутможно с ним ознакомиться. А если в двух словах, то этот шаблон очень полезен если вам нужно обойти коллекцию из указателей на абстрактный базовый класс, применив к ним какую то операцию в зависимости от типа, который скрывается за абстракцией. Поэтому перейдем сразу к недостаткам, которые хотели бы устранить.
template< class T >
struct AbstractVisitor { virtual ~AbstractVisitor() = default; virtual void visit( T& ) = 0; }; template< class ... T >
struct TypeList { }; template< class T > struct AbstractVisitor { virtual ~AbstractVisitor() = default; virtual void visit( T& ) = 0; }; template< class ...T > struct AbstractVisitors; template< class ... T > struct AbstractVisitors< TypeList< T... > > : AbstractVisitor< T >... { }; template< class Functor, class ... T >
struct Dispatcher; template< class Functor, class ... T > struct Dispatcher< Functor, TypeList< T... > > : AbstractVisitors< TypeList< T... > > { Dispatcher( Functor functor ) : functor( functor ) {} Functor functor; }; template< class Dispatcher, class T >
struct Resolver : AbstractVisitor< T > { void visit( T& obj ) override { static_cast< Dispatcher* >( this )->functor( obj ); }; }; template< class Functor, class ... T > struct Dispatcher< Functor, TypeList< T... > > : AbstractVisitors< TypeList< T... > >, Resolver< Dispatcher< Functor, TypeList< T... > >, T >... { Dispatcher( Functor functor ) : functor( functor ) {} Functor functor; }; template< class ... T >
struct AbstractVisitors< TypeList< T... > > : virtual AbstractVisitor< T >... { }; template< class Dispatcher, class T > struct Resolver : virtual AbstractVisitor< T > { void visit( T& obj ) override { static_cast< Dispatcher* >( this )->functor( obj ); }; }; struct Object1;
struct Object2; using ObjectList = TypeList< Object1, Object2 >; struct AbstractObject { virtual void accept( AbstractVisitors< ObjectList >& visitor ) = 0; }; struct Object1 : AbstractObject { void accept( AbstractVisitors< ObjectList >& visitor ) override { static_cast< AbstractVisitor< Object1 >& >( visitor ).visit( *this ); }; }; struct Object2 : AbstractObject { void accept( AbstractVisitors< ObjectList >& visitor ) override { static_cast< AbstractVisitor< Object2 >& >( visitor ).visit( *this ); }; }; void test( Object1& obj ) { std::cout << "1" << std::endl; } template< class T > void test( T& obj ) { std::cout << "2" << std::endl; } int main() { Object1 t1,t2,t3,t4; Object2 e1,e2,e3; std::vector< AbstractObject* > vector = { &t1, &e1, &t2, &t3, &e2, &e3, &t4 }; auto l = []( auto& obj ){ test(obj); }; Dispatcher<decltype(l), ObjectList> dispatcher; for( auto* obj : vector ) { obj->accept( dispatcher ); } } template< class TypeList >
struct Dispatchable { virtual ~Dispatchable() = default; virtual void accept( AbstractVisitors< TypeList >& ) = 0; template< class Functor > void dispatch( Functor functor ) { static Dispatcher< decltype(functor), TypeList > dispatcher( functor ); accept( dispatcher ); }; }; #define DISPATCHED( TYPE, TYPE_LIST ) \ void accept( AbstractVisitors< TYPE_LIST >& visitor ) override \ { \ static_cast< AbstractVisitor< TYPE >& >( visitor ).visit( *this ); \ } struct Object1;
struct Object2; using ObjectList = TypeList< Object1, Object2 >; struct AbstractObject : Dispatchable< ObjectList > { }; struct Object1 : AbstractObject { DISPATCHED( Object1, ObjectList ) }; struct Object2 : AbstractObject { DISPATCHED( Object2, ObjectList ) }; void test( Object1& obj )
{ std::cout << "1" << std::endl; } template< class T > void test( T& obj ) { std::cout << "2" << std::endl; } int main() { Object1 t1,t2,t3,t4; Object2 e1,e2,e3; std::vector< AbstractObject* > vector = { &t1, &e1, &t2, &t3, &e2, &e3, &t4 }; for( auto* obj : vector ) { obj->dispatch( []( auto& obj ) { test(obj); } ); } }
#include <type_traits>
#include <iostream> #include <vector> template< class ... T > struct TypeList { }; template< class T > struct AbstractVisitor { virtual ~AbstractVisitor() = default; virtual void visit( T& ) = 0; }; template< class ...T > struct AbstractVisitors; template< class ... T > struct AbstractVisitors< TypeList< T... > > : virtual AbstractVisitor< T >... { }; template< class Dispatcher, class T > struct Resolver : virtual AbstractVisitor< T > { void visit( T& obj ) override { static_cast< Dispatcher* >( this )->functor( obj ); }; }; template< class Functor, class ... T > struct Dispatcher; template< class Functor, class ... T > struct Dispatcher< Functor, TypeList< T... > > : AbstractVisitors< TypeList< T... > >, Resolver< Dispatcher< Functor, TypeList< T... > >, T >... { Dispatcher( Functor functor ) : functor( functor ) {} Functor functor; }; template< class TypeList > struct Dispatchable { virtual ~Dispatchable() = default; virtual void accept( AbstractVisitors< TypeList >& ) = 0; template< class Functor > void dispatch( Functor functor ) { static Dispatcher< decltype(functor), TypeList > dispatcher( functor ); accept( dispatcher ); }; }; #define DISPATCHED( TYPE, TYPE_LIST ) \ void accept( AbstractVisitors< TYPE_LIST >& visitor ) override \ { \ static_cast< AbstractVisitor< TYPE >& >( visitor ).visit( *this ); \ } struct Object1; struct Object2; using ObjectList = TypeList< Object1, Object2 >; struct AbstractObject : Dispatchable< ObjectList > { }; struct Object1 : AbstractObject { DISPATCHED( Object1, ObjectList ) }; struct Object2 : AbstractObject { DISPATCHED( Object2, ObjectList ) }; void test( Object1& obj ) { std::cout << "1" << std::endl; } template< class T > void test( T& obj ) { std::cout << "2" << std::endl; } int main() { Object1 t1,t2,t3,t4; Object2 e1,e2,e3; std::vector< AbstractObject* > vector = { &t1, &e1, &t2, &t3, &e2, &e3, &t4 }; for( auto* obj : vector ) { obj->dispatch( []( auto& obj ) { test(obj); } ); } } =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 27-Апр 11:41
Часовой пояс: UTC + 5