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

    Хочу рассказать как мы использовали списки базовых типов для обработки сообщений. Сообщения представляют собой структуры, унаследованные от небольших базовых структур. Вся полезная информация хранится в базовых структурах. Для обработки нужно знать от каких базовых структур было унаследовано обрабатываемое сообщение. Все что нужно для работы со списками типов мы нашли в 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) );
    
    }
    


    Update:
    Замечание к BOOST_STATIC_ASSERT в шаблонном Describer::operator()
    На данный пример G++ выдаст ошибку компиляции на BOOST_STATIC_ASSERT(false). G++, в отличии от MS Visual C++, проверяет тело шаблона, даже если он не будет инстанцирован. Все не зависящие от шаблонного параметра имена должны быть известны на момент определения шаблона. Если какая-то конструкция вызывает ошибку компиляции и не зависит от шаблонного параметра, то ошибка компиляции будет. Можно поступить следующим образом:
    template <typename T>
    struct Describer
    {
        template<typename U>
        void operator()
        {
            BOOST_STATIC_ASSERT(sizeof(U) == 0);
        }
    }
    



    Update2: Спасибо nickolaym за интересные комментарии с вариантом автоматического формирования списка базовых классов.

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 4

      +1
      boost::mpl::inherit не предполагается задействовать?
      Тогда можно автоматизировать создание списка баз.
      template<class... Bases> struct derived : boost::mpl::inherit<Bases...>::type
      {
      	typedef boost::mpl::vector<Bases...> types;
      };
      
      class A {.....};
      class B {.....};
      class C {.....};
      
      class Derived : public derived<A,B,C> {.....};
      
        0
        В проекте у базовых структур нет конструкторов по-умолчанию, а boost::mpl::inherit требует наличия конструктора по-умолчанию, так что boost::mpl::inherit не использовал, а в стать вполне можно.
        Спасибо за замечание.
          +1
          Немножко колдунства и рукоделия
          // тэг для вызова конструктора без параметров
          enum skipctor_type { skipctor };
          
          // boost::mpl::inherit своими руками
          template<class... Bases> struct derived_;
          template<> struct derived_<> {};
          template<class Base, class... Bases> struct derived_<Base,Bases...>
          	: Base, derived_<Bases...>
          {
          	// дефолт-конструктор предполагает дефолт-конструктор у всех баз, начиная с этой
          	derived_()
          		{}
          	
          	// скип-конструктор - эту базу по дефолту, остальные по списку
          	template<class... Args>
          	derived_(skipctor_type arg, Args&&... args)
          		: derived_<Bases...>(args...)
          		{}
          	
          	// все базы по списку
          	template<class Arg, class... Args>
          	derived_(Arg&& arg, Args&&... args)
          		: Base(arg)
          		, derived_<Bases...>(args...)
          		{}
          };
          
          
          
          template<class... Bases> struct derived : derived_<Bases...>
          {
          	typedef derived allbases;
          	typedef boost::mpl::vector<Bases...> baseclasses;
          	
          	derived() {}
          	
          	template<class... Args>
          	derived(Args&&... args)
          		: derived_<Bases...>(args...)
          		{}
          };
          
          struct A { A(int,int,int) {} };
          struct B { };
          struct C { C(int) {} };
          
          struct D : derived<A,B,C>
          {
          	D() : allbases(A(1,2,3),skipctor,4) {}
          };
          

          Создание иерархии — можно было, как это сделано в inherit_linearly, выразить через fold, но проще самому рекурсию написать, чем морочить голову.

          С конструкторами — поддержать дефолтные и одноаргументные конструкторы (в частности, конструктор копирования или перемещения) легко, а вот чтобы транслировать кортежи в вариадики — т.е. чтобы можно было написать что-то в таком роде
          struct D : derived<A,B,C>
          {
          //	D() : allbases(A(1,2,3),         skipctor,    4           ) {}
          	D() : allbases(ctor_args(1,2,3), ctor_args(), ctor_args(4)) {}
          };
          

          это уже мозг немножко сломается. Теоретически, можно, но всё равно — для уже существующих замороженных базовых классов придётся выражать через конструкторы копирования-перемещения, а если их можно подправить, то проще добавить к их конструкторам ещё и конструкторы с аргументом tuple или initializer_list.
            0
            Впечатляет, спасибо!

      Only users with full accounts can post comments. Log in, please.