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

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

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

// (2) External function

run(CallbackConverter(ExternalHandler, &executor));

// (3) Static method

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

// (4) Member merthod

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

// (6) lambda-expression

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

<p>4.3. Вызовы в алгоритмах</p><p>4.3.1. Описание проблемы</p>

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

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

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

<p>4.3.2. Параметризация типов</p>

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

Поясним сказанное на примере. Предположим, мы реализовали алгоритм сортировки пузырьком (Листинг 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                    // (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). Теперь мы можем делать сортировку для любого типа данных: мы просто вызываем функцию и передаем ей требуемую переменную-массив, а компилятор сгенерирует код для соответствующего массива.

<p>4.3.3. Объявление предикатов</p>
Перейти на страницу:

Похожие книги