Pull to refresh

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

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

Tags:
Hubs:
+9
Comments4

Articles

Change theme settings