Группы

Vladimir Migashko edited this page Dec 26, 2013 · 29 revisions

Главная, Основные концепции, АОП, Адвайсы

Мощный механизм для работы с группами адвайсов объединенных по определенным критериям. В группу можно поместить один или несколько адвайсов с помощью конструкции fas::group:

typedef fas::group<_group_, _foo_> group_advice1;
typedef fas::group<_group_, fas::type_list_n<_bar_, _baz_>::type > group_advice2;

Адвайсы определяются в группу одним или несколькими конструкциями fas::group с общим тегом. В отличие от прочих типов адвайсов, в аспекте может быть несколько таких конструкций с общим тегом. В аспекте может быть определено неограниченное количество групп.

Если все адвайсы группы поддерживают семантику operator()(T&[, arg1, [arg2, [arg3, [arg4, [arg5]]]]), то вызов может быть осуществлен прозрачно, например:

t.get_aspect().template get<_group_>()(t, arg1);

Т.е. в этом месте нет возможности узнать, что скрыто тегом _group_, группа адвайсов, или отдельный адвайс. Можно указать явно, что предполагается обращение к группе:

t.get_aspect().template getg<_group_>()(t, arg1);

Однако и в этом случае, под тегом _group_ может быть не группа, а конкретный адвайс. Разница в том, что в первом случае, если в аспекте отсутствует сущность с тегом _group_, компилятор выдаст ошибку fas::advice_not_found. Во втором случае вызов будет проигнорирован. Избежать ошибку компиляции можно путем внедрения в аспект заглушки. Оба варианта, get с заглушкой и getg, взаимозаменяемы. В обоих случаях, как правило, предполагается, что разработчик аспектного класса, предлагает пользователю внедрить некоторый свой функционал. В первом случае это может быть некоторый функционал расширяющий существующий, а во втором - реакция на некоторые события.

Адвайсы группы обрабатываются строго в том порядке, в котором они определены в аспекте, т.е. первым будет вызван последний внедренный в аспект адвайс. Если необходимо извинить порядок на обратный - сымитировать реакцию на событие, то используется специальный метод gete:

t.get_aspect().template gete<_group_>()(t, arg1);

Шаблонными параметрами getg и gete, помимо тегов групп и адвайсов можно передавать списки тегов, в который включены как теги групп, так и обычных адвайсов. В примере ниже показано как это можно сделать:

#include <fas/aop.hpp>
#include <fas/algorithm.hpp>

#include <iostream>

struct _show1_;
struct _show2_;
struct _show3_;

struct _call_group_;

template<int I>
struct ad_show
{
  template<typename T>
  void operator()(T&)
  {
    std::cout << "show " << I << std::endl;
  }
};

struct aspect_foo: fas::aspect< fas::type_list_n<
  fas::advice<_show1_, ad_show<1> >,
  fas::advice<_show2_, ad_show<2> >,
  fas::advice<_show3_, ad_show<3> >,
  fas::group<_call_group_, fas::type_list_n<_show1_, _show2_>::type >,
  fas::group<_call_group_, _show3_>
>::type>{};

template< typename A = fas::aspect<> >
class foo
  : public fas::aspect_class< typename fas::merge_aspect<A, aspect_foo>::type >
{
  typedef fas::aspect_class< typename fas::merge_aspect<A, aspect_foo>::type > super;
public:
  void method1()
  {
    this->get_aspect().template get<_call_group_>()(*this);
  }

  void method2()
  {
    this->get_aspect().template getg<_call_group_>()(*this);
  }
  
  void method3()
  {
    this->get_aspect().template gete<_call_group_>()(*this);
  }

  void method4()
  {
    typedef typename super::aspect::template select_group<_call_group_>::type group_list_origin;
    typedef typename fas::random_shuffle< fas::int_<42>, group_list_origin >::type shuffle_list;
    typedef typename fas::type_list_n< _show3_, shuffle_list>::type group_list;
    this->get_aspect().template getg<group_list>()(*this);
  }
};

struct aspect_bar: fas::aspect< fas::type_list_n<
  fas::advice<_show3_, ad_show<33> >,
  fas::alias<_call_group_, _show3_ >
>::type>{};


int main()
{
  foo<> f;
  std::cout << "foo<>::method1()" << std::endl;
  f.method1();
  std::cout << "foo<>::method2()" << std::endl;
  f.method2();
  std::cout << "foo<>::method3()" << std::endl;
  f.method3();
  std::cout << "foo<>::method4()" << std::endl;
  f.method4();
  
  std::cout << "-------" << std::endl;
  foo<aspect_bar> b;
  std::cout << "foo<aspect_bar>::method1()" << std::endl;
  b.method1();
  std::cout << "foo<aspect_bar>::method2()" << std::endl;
  b.method2();
  std::cout << "foo<aspect_bar>::method3()" << std::endl;
  b.method3();
  std::cout << "foo<aspect_bar>::method4()" << std::endl;
  b.method4();
  
  std::cout << "-------" << std::endl;
  foo< fas::remover<_call_group_> > r;
  std::cout << "foo<fas::remover<_call_group_>>::method1()" << std::endl;
  // r.method1();
  std::cout<<"error: incomplete type ‘fas::advice_has_been_removed<_call_group_>’ used in nested name specifier" << std::endl;
  std::cout << "foo<fas::remover<_call_group_>>::method2()" << std::endl;
  r.method2();
  std::cout << "foo<fas::remover<_call_group_>>::method3()" << std::endl;
  r.method3();
  std::cout << "foo<fas::remover<_call_group_>>::method4()" << std::endl;
  r.method4();
}

В этом примере был сформирован аспект aspect_foo для класса foo который включает три адвайса, которые выводят некоторое число и группа, в которую включены все три адвайса. Группа определена двумя конструкциями fas::group, но ничто не мешает определить одной такой конструкцией, например так:

fas::group<_call_group_, fas::type_list_n<_show1_, _show2_, _show3_>::type >,

или тремя:

fas::group<_call_group_, _show1_>,
fas::group<_call_group_, _show2_>,
fas::group<_call_group_, _show3_>

В классе foo определены четыре метода:

  • Метод method1 обращается к _call_group_ как к обычному адвайсу. Если адвайс с тегом _call_group_ в аспекте отсутствует, то будет ошибка компиляции
  • Метод method2 обращается к _call_group_ как к группе. Если адвайс с тегом _call_group_ в аспекте отсутствует, то ошибки не будет, вызов будет проигнорирован
  • Метод method3 эмулирует событие _call_group_ - адвайсы группы будут вызваны в обратном порядке
  • Метод method4 формирует собственную группу

Формирование списка вызова в method4 происходит следующим образом:

  • извлекаются из аспекта все теги группы _call_group_
  • псевдо-случайно перемешиваются с зерном 42
  • дополнительно добавляется тег _show3_, после чего осуществляется вызов.

Следует отметить, что при повторном добавлении тега в группу в аспекте, происходит только изменение порядка вызовов адвайсов группы, т.е. повторного вызова не происходит. Дважды вызвать один и тот же адвайс возможно только при ручном формировании списка, как это продемонстрировано в этом методе.

Результатом вызова всех четырех методов с аспектом foo_aspect будет:

foo<>::method1()                                                                                                                                                               
show 1                                                                                                                                                                         
show 2                                                                                                                                                                         
show 3                                                                                                                                                                         
foo<>::method2()                                                                                                                                                               
show 1                                                                                                                                                                         
show 2                                                                                                                                                                         
show 3                                                                                                                                                                         
foo<>::method3()                                                                                                                                                               
show 3                                                                                                                                                                         
show 2                                                                                                                                                                         
show 1
foo<>::method4()
show 3
show 1
show 3
show 2

В аспекте aspect_bar под тегом _show3_ внедряется адвайс выводящий на консоль число 33, а под _call_group_ алиас на этот адвайс. После внедрения aspect_bar в foo изначальная группа _call_group_ будет заменена этим алиасом. Результатом внедрения будет следующий вывод на консоль:

foo<aspect_bar>::method1()
show 33
foo<aspect_bar>::method2()
show 33
foo<aspect_bar>::method3()
show 33
foo<aspect_bar>::method4()
show 33
show 33

Ну и наконец, в примере продемонстрировано удаление группы. После удаления, вызов method1, приведет к ошибке компиляции:

foo<fas::remover<_call_group_>>::method1()
error: incomplete type ‘fas::advice_has_been_removed<_call_group_>’ used in nested name specifier
foo<fas::remover<_call_group_>>::method2()
foo<fas::remover<_call_group_>>::method3()
foo<fas::remover<_call_group_>>::method4()
show 3

Несмотря на то, что мы используем термин "вызов", в случае с включенной оптимизацией, как правило, будет произведена inline подстановка кода всех адвайсов группы, также как и при обращению к обычному адвайсу.