Выбрать главу

4.3.4. Предикаты по умолчанию

Итак, мы рассмотрели, как с помощью предикатов реализуется операция вычисления меньшего из двух элементов. Но далеко не всегда требуется сортировать сложные структуры данных, зачастую это всего лишь обычные числовые значения. В этом случае придется объявлять предикат с тривиальной реализацией (сравнить два числа). Может также случиться, что у нас в объявлении элемента данных уже реализован перегруженный оператор сравнения, тогда в предикате придется дублировать его код. Всего этого можно избежать, если объявить предикат, который будет использоваться по умолчанию. Реализация приведена в Листинг 35.

Листинг 35. Шаблон с предикатом по умолчанию

template <typename Data>  // (1)

struct default_less

{

  bool operator()(const Data& x, const Data& y)  // (2)

  {

    return x < y;

  }

};

template <typename Data, typename Predicate = default_less<Data>>        // (3)

void sort_bubble(Data* data, size_t size, Predicate less = Predicate())  // (4)

{

  for (size_t i = 0; i < size – 1; i++)

  {

    for (size_t j = 0; j < size – i – 1; j++)

    {

      if (less (data[j + 1], data[j]))

      {

        Data temp = data[j];

        data[j] = data[j + 1];

        data[j + 1] = temp;

      }

    }

  }

}

В строке 1 объявлен шаблон для структуры, реализующей предикат сравнения. В этой структуре перегружен оператор (строка 2), который возвращает результат сравнения двух аргументов. Он будет корректно работать как для чисел, так и для объектов, в которых перегружен оператор «меньше».

В строке 3 объявлен шаблон для функции сортировки. Первый параметр шаблона – это тип данных, которые необходимо сортировать, а второй параметр – это тип предиката. По умолчанию типом предиката является структура, объявленная выше, которая инстанциируется соответствующим типом данных.

В строке 4 объявлена функция шаблона. Первый параметр здесь – это данные для сортировки, а второй параметр – предикат для вычисления меньшего элемента. Если при вызове функции предикат не задан, то в качестве значения по умолчанию будет подставлена переменная – экземпляр структуры, объявленной в строке 1. Инстанциироваться эта структура будет типом Data, переданным как первый параметр шаблона.

Итак, на примере алгоритма сортировки мы рассмотрели, как реализуются предикаты для выбора меньшего элемента из двух. Подобным образом можно реализовать множество других операций: сравнения, сложения, вычисления хэш-суммы и т. п. Таким образом, предикаты предлагают удобный способ реализации арифметико-логических операций с нечисловыми типами данных. Частично снимается проблема монолитной архитектуры при использовании функциональных объектов: мы можем реализовать любое количество нужных объектов и подставлять их в шаблон по мере необходимости19. И в заключение отметим, что концепция предикатов широко используется в реализации алгоритмов стандартной библиотеки STL.

4.4. Асинхронные вызовы

4.4.1. Инициатор

Также, как мы делали при анализе синхронных вызовов, проанализируем различные реализации инициатора асинхронных вызовов (Листинг 36, некоторые фрагменты кода пропущены, чтобы не загромождать описание).

Листинг 36. Реализации инициатора асинхронных вызовов для различных типов аргументов

class Executor;

class CallbackHandler

{

public:

  void operator() (int eventID);

};

//Pointer to function

class Initiator1

{

public:

  using ptr_callback = void(*) (int, void*);

  void setup(ptr_callback pPtrCallback, void* pContextData) ;

private:

  ptr_callback ptrCallback = nullptr;

  void* contextData = nullptr;

};

//Pointer to the class static method

class Initiator2

{

public:

  using ptr_callback_static = void(*) (int, Executor*);

  void setup(ptr_callback_static pPtrCallback, Executor* pContextData) ;

private:

      ptr_callback_static ptrCallback = nullptr;

      Executor* contextData = nullptr;

};

//Pointer to the class member method

class Initiator3

{

public:

  using ptr_callback_method = void(Executor::*)(int);

вернуться

19

Мы употребили термин «частично», потому что полной независимости здесь нет: при изменении функционального объекта нужно перекомпилировать как инициатор, так и исполнитель. Таким образом, независимость здесь обеспечивается только на уровне исходного кода.