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

  run(FunctionConverter(ExternalHandler, &executor));

  // (3) Static method

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

  using StaticConverter = CallbackConverter<StaticPointer, Executor*>;

  run(StaticConverter(Executor::staticCallbackHandler, &executor));

  // (4) Member merthod

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

  using MethodConverter = CallbackConverter<MethodPointer, Executor>;

  run(MethodConverter(&Executor::callbackHandler, &executor));

  // (5) Functional object

  run(executor);

  // (6) lambda-expression

  auto lambda = [capturedValue](int eventID) {/*it will be called by initiator*/};

  run(lambda);

}

В строке 1 объявлен класс исполнителя, в котором определены все необходимые типы вызовов: статический метод, метод-член, перегруженный оператор.  Для вызовов 2, 3 и 4 в качестве аргумента передается функциональный объект для преобразования, который инстанциируется соответствующими типами. В остальных случаях нужный аргумент передается непосредственно, преобразования вызовов там не нужно. При использовании лямбда-выражения (строка 6) компилятор неявно определит его тип и подставит его в функцию шаблона-инициатора как аргумент.

При использовании преобразования вызовов можно использовать сокращенную запись без дополнительного объявления промежуточных типов, в этом случае код получается более компактным, но более запутанным (см. Листинг 30)

Листинг 30. Преобразование вызовов без объявления промежуточных типов

// (2) External function

run(CallbackConverter<void(*)(int, void*), void*>(ExternalHandler, &executor));

// (3) Static method

run(CallbackConverter<void(*)(int, Executor*), Executor*>(Executor::staticCallbackHandler, &executor));

// (4) Member merthod

run(CallbackConverter<void(Executor::*)(int), Executor>(&Executor::callbackHandler , &executor));

// (6) lambda-expression

run([capturedValue](int eventID) {/*it will be called by initiator*/});

4.3. Вызовы в алгоритмах

4.3.1. Описание проблемы

Алгоритмы – краеугольный камень информатики, они встречаются практически во всех ее разделах. Таким образом, проектирование и разработка алгоритмов – одна из важнейших задач как в теоретической науке, так и в инженерной практике.

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

Например, предположим, что мы написали код для алгоритма сортировки. Естественно предположить, что он будет сортировать числа. Но вот появилась новая задача: отсортировать строки. По сравнению с исходной реализацией у нас теперь другая структура данных (строки) и новые правила сравнения (строки сравниваются совсем не так, как числа). А ведь в будущем, возможно, появятся более сложные случаи – например, сортировка структур по отдельным полям… Как написать универсальный код, работающий с любыми типами данных?

4.3.2. Параметризация типов

Обозначенная выше проблема в рамках параметрического полиморфизма решается просто: код оформляется в виде шаблона, параметрами шаблона выступают типы данных. При инстанциировании шаблона генерируется код, в который подставляются соответствующие типы.

Поясним сказанное на примере. Предположим, мы реализовали алгоритм сортировки пузырьком (Листинг 31).

Листинг 31. Сортировка массива методом пузырька

void sort_bubble(int* data, size_t size)

{

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

  {

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

    {

      if (data[j + 1] < data[j])

      {

        int temp = data[j];

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

        data[j + 1] = temp;

      }

    }

  }

}

Описанный код работает с числами. Параметризуем типы (Листинг 32):

Листинг 32. Параметризация типов для сортировки пузырьком

template <typename Data>                   // (1)

void sort_bubble(Data* data, size_t size)  // (2)

{

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

  {

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

    {

      if (data[j + 1] < data[j])

      {

        Data temp = data[j]; // (3)

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

        data[j + 1] = temp;

      }

    }

  }

}

По сравнению с предыдущим листингом изменений здесь совсем немного: в строке 1 объявлен параметр шаблона для типа данных, в реализации функции вместо типа данных подставляется параметр шаблона (строки 2 и 3). Теперь мы можем делать сортировку для любого типа данных: мы просто вызываем функцию и передаем ей требуемую переменную-массив, а компилятор сгенерирует код для соответствующего массива.