Обработка структуры по списку базовых типов06.01.2015 19:18
Хочу рассказать как мы использовали списки базовых типов для обработки сообщений. Сообщения представляют собой структуры, унаследованные от небольших базовых структур. Вся полезная информация хранится в базовых структурах. Для обработки нужно знать от каких базовых структур было унаследовано обрабатываемое сообщение. Все что нужно для работы со списками типов мы нашли в 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 types;
};
struct Derived23: public Base2, public Base3
{
boost: mpl: vector 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(Describer ());
}
В результате исполнения будет
выведено
Получение информации из Base1
Получение информации из Base2
У такого ваианта есть две проблемы:
Значение d12 никак не используется;
Функция boost: mpl: for_each создает экземпляры базовых структур.
С первой проблемой все просто — пусть значение передается и сохраняется на конструкторе Describer, а сам класс будет шаблонным. Вторая проблема более серьезная, так как кроме затрат на создание объектов дополнительно накладываются ограничения на структуры — они должны иметь конструктор без параметров и не могут быть абстрактными. Я решил, перегрузить operator () по указателю. В этом случае список типов должен содержать указатели на типы или можно воспользоваться вторым вариантом for_each, с передачей шаблона для трансформации, этим вариантом и воспользуемся. В качестве шаблона для трансформации возьмем boost: add_pointer. Для проверки что обрабатываются все базовые структуры добавим шаблонный operator (), содержащий BOOST_STATIC_ASSERT (false). Это даст ошибку компиляции если появится новая базовая структура. В итоге получим:
template
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
void operator ()(U*)
{
BOOST_STATIC_ASSERT (false);
}
private:
const T& v;
};
void main ()
{
Derived12 d12;
boost: for_each< Derived12::types,
boost::add_pointer >
(Describer(d12));
}
Теперь попробуем упростить заведение списков типов, участвующих в наследовании. Объявим полный список типов базовых структур и воспользуемся алгоритмом boost: mpl: copy_if. Который скопирует в новый список все элементы, удовлетворяющие указанному условию. В качестве условия возьмем проверку на наследование boost: is_base_of.
typedef boost: mpl: vector FullTypesList;
template
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:: TypesList,
add_pointer >(boost: ref (*this));
}
Обертка boost: ref нужна, чтобы не вызвался оператор копирования для Describer.Окончательный вариант
struct Base1 {};
struct Base2 {};
struct Base3 {};
typedef boost: mpl: vector FullTypesList;
template
struct MakeTypesList
{
typedef typename boost: mpl: copy_if<
BaseList,
boost::is_base_of< boost::mpl::_, T > >:: type TypesList;
};
template
class Describer
{
public:
Describer (const T& v):
v (v)
{}
void operator ()()
{
boost: mpl: for_each<
typename MakeTypesList:: TypesList,
add_pointer >(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
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(mes12))();
}
Если классов обрабатывающих структуры подобным образом много, то разумнее объявить списки базовых классов для сообщений отдельно. У нас получилось, что структура сообщения не используется самостоятельно — она является базовым классом для шаблонного класса, реализующего общий интерфейс всех сообщений и в нем мы определяем список базовых типов. К этому списку и обращаемся при вызове for_each. Можно сделать шаблон-обертку и использовать его. Простой вариант
шаблона-обертки
template
struct Wrapper: public T
{
typedef typename MakeTypesList:: TypesList TypesList;
}
void Describer: operator ()
{
boost: mpl: for_each<
typename T::TypesList,
add_pointer >(boost: ref (*this));
}
© Habrahabr.ru