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

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

Вихідні дані тут ті ж що і у попередній статті. Прихований текст
struct Base1 {};
struct Base2 {};
struct Base3 {};

struct Deliver12: public Base1, public Base2 {};
struct Deliver23: public Base2, public Base3 {};
В реальності, у нас і базових структур, і повідомлень, створених на їх основі, набагато більше.
У найпростішому варіанті для boost::mpl::for_each потрібно вказати в якості шаблонного параметра — список типів, а в якості аргументу — клас з методом operator()(T), де T — тип зі списку. Можна зробити метод шаблонним, але це не зовсім те що нам потрібно. Тому перегрузим operator() для всіх базових структур. Список типів поки в ручну оголосимо у кожному повідомленні. У першому наближенні, отримуємо:
struct Deliver12: public Base1, public Base2
{
boost::mpl::vector<Base1, Base2> types;
};

struct Deliver23: 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()
{
Deliver12 d12;
boost::for_each<Deliver12::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()
{
Deliver12 d12;

boost::for_each< Deliver12::types, 
boost::add_pointer<boost::mpl::_> >
( Describer<Deliver12>(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;
};

//Списки типів Deliver12 і Deliver23 більше не потрібні.
struct Deliver12: public Base1, public Base2 {};
struct Deliver23: public Base2, public Base3 {};

void main()
{
Deliver12 mes12;
( Describer<Deliver12>(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) );

}


Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.