Обработка структуры по списку базовых типов

—

от автора

Хочу рассказать как мы использовали списки базовых типов для обработки сообщений. Сообщения представляют собой структуры, унаследованные от небольших базовых структур. Вся полезная информация хранится в базовых структурах. Для обработки нужно знать от каких базовых структур было унаследовано обрабатываемое сообщение. Все что нужно для работы со списками типов мы нашли в Boost.MPL. В качестве списка типов выбрали boost::mpl::vector. Для прохода по списку типов boost::mpl::for_each.

Исходные данные здесь те же что и в предыдущей статье.

Скрытый текст

struct Base1 {}; struct Base2 {}; struct Base3 {};  struct Derived12: public Base1, public Base2 {}; struct Derived23: public Base2, public Base3 {}; 

В реальности, у нас и базовых структур, и сообщений, созданных на их основе, гораздо больше.

В самом простом варианте для boost::mpl::for_each нужно указать в качестве шаблонного параметра — список типов, а в качестве аргумента — класс с методом operator()(T), где T — тип из списка. Можно сделать метод шаблонным, но это не совсем то что нам нужно. Поэтому перегрузим operator() для всех базовых структур. Список типов пока в ручную объявим в каждом сообщении. В первом приближении, получаем:

struct Derived12: public Base1, public Base2 {     boost::mpl::vector<Base1, Base2> types; };  struct Derived23: public Base2, public Base3 {     boost::mpl::vector<Base2, Base3> types; };  class Describer { public:     void operator()(Base1)     {         std::cout << "Получение информации из Base1\n";     }      void operator()(Base2)     {         std::cout << "Получение информации из Base2\n";     }      void operator()(Base3)     {         std::cout << "Получение информации из Base3\n";     } };  void main() {     Derived12 d12;     boost::for_each<Derived12::types>(Describer()); } 

В результате исполнения будет

выведено

 Получение информации из Base1 Получение информации из Base2 

У такого ваианта есть две проблемы:

  1. Значение d12 никак не используется;
  2. Функция boost::mpl::for_each создает экземпляры базовых структур.

С первой проблемой все просто — пусть значение передается и сохраняется на конструкторе Describer, а сам класс будет шаблонным. Вторая проблема более серьезная, так как кроме затрат на создание объектов дополнительно накладываются ограничения на структуры — они должны иметь конструктор без параметров и не могут быть абстрактными. Я решил, перегрузить operator() по указателю. В этом случае список типов должен содержать указатели на типы или можно воспользоваться вторым вариантом for_each, с передачей шаблона для трансформации, этим вариантом и воспользуемся. В качестве шаблона для трансформации возьмем boost::add_pointer. Для проверки что обрабатываются все базовые структуры добавим шаблонный operator(), содержащий BOOST_STATIC_ASSERT(false). Это даст ошибку компиляции если появится новая базовая структура. В итоге получим:

template<typename T> class Describer { public:     Describer(const T& v):         v(v)     {}      void operator()(Base1*)     {         std::cout << "Получение информации из Base1\n";     }      void operator()(Base2*)     {         std::cout << "Получение информации из Base2\n";     }      void operator()(Base3*)     {         std::cout << "Получение информации из Base3\n";     }      template<typename U>     void operator()(U*)     {         BOOST_STATIC_ASSERT(false);     } private:     const T& v; };  void main() {     Derived12 d12;      boost::for_each< Derived12::types,                       boost::add_pointer<boost::mpl::_> >         ( Describer<Derived12>(d12) ); } 

Теперь попробуем упростить заведение списков типов, участвующих в наследовании. Объявим полный список типов базовых структур и воспользуемся алгоритмом boost::mpl::copy_if. Который скопирует в новый список все элементы, удовлетворяющие указанному условию. В качестве условия возьмем проверку на наследование boost::is_base_of.

typedef boost::mpl::vector<Base1, Base2, Base3> FullTypesList;  template<typename T, typename BaseList> struct MakeTypesList {     typedef typename boost::mpl::copy_if<         BaseList,         boost::is_base_of< boost::mpl::_, T > >::type TypesList; }; 

Для удобства добавим в Describer operator() без параметров, который будет вызывать for_each.

void Describer::operator()() {     boost::mpl::for_each<         typename MakeTypesList<T,FullTypesList>::TypesList,         add_pointer<boost::mpl::_> >( boost::ref(*this) ); } 

Обертка boost::ref нужна, чтобы не вызвался оператор копирования для Describer.

Окончательный вариант

struct Base1 {}; struct Base2 {}; struct Base3 {};  typedef boost::mpl::vector<Base1, Base2, Base3> FullTypesList;  template<typename T, typename BaseList> struct MakeTypesList {     typedef typename boost::mpl::copy_if<         BaseList,         boost::is_base_of< boost::mpl::_, T > >::type TypesList; };  template<typename T> class Describer { public:     Describer(const T& v):         v(v)     {}      void operator()()     {         boost::mpl::for_each<             typename MakeTypesList<T,FullTypesList>::TypesList,             add_pointer<boost::mpl::_> >( boost::ref(*this) );     }      void operator()(Base1*)     {         std::cout << "Получение информации из Base1\n";     }      void operator()(Base2*)     {         std::cout << "Получение информации из Base2\n";     }      void operator()(Base3*)     {         std::cout << "Получение информации из Base3\n";     }      template<typename U>     void operator()(U*)     {         BOOST_STATIC_ASSERT(false);     } private:     const T& v; };  //Списки типов в Derived12 и Derived23 больше не нужны. struct Derived12: public Base1, public Base2 {}; struct Derived23: public Base2, public Base3 {};  void main() {     Derived12 mes12;     ( Describer<Derived12>(mes12) )(); } 

Если классов обрабатывающих структуры подобным образом много, то разумнее объявить списки базовых классов для сообщений отдельно. У нас получилось, что структура сообщения не используется самостоятельно — она является базовым классом для шаблонного класса, реализующего общий интерфейс всех сообщений и в нем мы определяем список базовых типов. К этому списку и обращаемся при вызове for_each. Можно сделать шаблон-обертку и использовать его. Простой вариант

шаблона-обертки

template<typename T> struct Wrapper: public T {     typedef typename MakeTypesList<T, FullTypesList>::TypesList TypesList; }  void Describer::operator() {     boost::mpl::for_each<         typename T::TypesList,         add_pointer<boost::mpl::_> >( boost::ref(*this) );  } 

ссылка на оригинал статьи http://habrahabr.ru/post/247365/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *