Pull to refresh

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

C++
Хочу рассказать как мы использовали списки базовых типов для обработки сообщений. Сообщения представляют собой структуры, унаследованные от небольших базовых структур. Вся полезная информация хранится в базовых структурах. Для обработки нужно знать от каких базовых структур было унаследовано обрабатываемое сообщение. Все что нужно для работы со списками типов мы нашли в 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:C++boost
Hubs: C++
Total votes 13: ↑11 and ↓2 +9
Views9.7K

Popular right now

Разработчик C++/Qt Middle
from 180,000 to 250,000 ₽1 CEORemote job
Разработчик C++ встраиваемые системы
from 180,000 to 250,000 ₽1 CEOСанкт-ПетербургRemote job
Разработчик С++
from 100,000 ₽Enaza GroupПермьRemote job
Разработчик C++
from 290,000 to 300,000 ₽ВГТМосква