Как действительно тривиальная демонстрация, давайте рассмотрим быструю сортировку. Мы выбираем значение (обычно называемое «осью поворота») и разделяем входную коллекцию на те, которые сравниваются меньше, чем точка поворота, и те, которые сравниваются больше или равны с осью .
В стандартной библиотеке уже есть std::partition
, который может выполнять разбиение на разделы - отделить коллекцию к тем элементам, которые удовлетворяют заданному условию, а те, которые этого не делают. Итак, чтобы сделать наше разбиение, нам просто нужно предоставить подходящий предикат.
В этом случае нам нужно простое сравнение: return x < pivot;
. Однако передача значения поворота каждый раз становится затруднительной. std::partition
просто передает значение из коллекции и спрашивает: «Это проходит ваш тест или нет?» Вам не нужно указывать std::partition
, что такое текущее значение поворота, и передать его в свою рутину при ее вызове. То, что можно было бы сделать, конечно (например, многие функции перечисления в Windows работают таким образом), но он становится довольно неуклюжим.
Когда мы вызываем std::partition
, мы уже выбрали значение поворота. Мы хотим, чтобы ... связать это значение с одним из параметров, который будет передан функции сравнения. Один очень некрасиво способ сделать это было бы «передать» его через глобальную переменную:
int pivot;
bool pred(int x) { return x < pivot; }
void quick_sort(int *begin, int *end) {
if (end - begin < 2)
return;
pivot = choose_pivot(begin, end);
int *pos = std::partition(begin, end, pred);
quick_sort(begin, pos);
quick_sort(pos, end);
}
Я действительно надеюсь, я не указать на то, что мы бы не использовать для этого глобального если мы сможем это сделать. Один довольно простой способ избежать этого - создать объект функции. Переходим текущее значение поворота, когда мы создаем объект, и сохраняет это значение в качестве состояния в объекте:
class pred {
int pivot;
public:
pred(int pivot) : pivot(pivot) {}
bool operator()(int x) { return x < pivot; }
};
void quick_sort(int *begin, int *end) {
if (end-begin < 2)
return;
int pivot = choose_pivot(begin, end);
int *pos = std::partition(begin, end, pred(pivot));
quick_sort(begin, pos);
quick_sort(pos, end);
}
Это добавил чуть-чуть дополнительного кода, но взамен мы устранили общемировом - довольно разумный обмен.
Конечно, с C++ 11 мы можем сделать еще немного лучше - язык добавил «лямбда-выражения», которые могут создать класс, очень похожий на нас. Используя это, наш код выглядит примерно так:
void quick_sort(int *begin, int *end) {
if (end-begin < 2)
return;
int pivot = find_pivot(begin, end);
auto pos = std::partition(begin, end, [pivot](int x) { return x < pivot; });
quick_sort(begin, pos);
quick_sort(pos, end);
}
Это изменяет синтаксис мы используем, чтобы определить класс/создать объект-функцию, но она по-прежнему в значительной степени та же самая основная идея, как и предыдущий кода: компилятор генерирует класс с конструктором и operator()
. Значения, которые мы заключим в квадратных скобках, передаются конструктору, а (int x) { return x < pivot; }
в основном становится телом operator()
для этого класса .
Это делает код намного проще для записи и гораздо легче читать, но он не меняет основного факта, что мы создаем объект, «захватывая» некоторое состояние в конструкторе и используя перегруженный operator()
для сравнения.
Конечно, сравнение похоже на то, что нам нужно для таких вещей, как сортировка. Он является общим употреблением лямбда-выражений и объектов функций в целом, но мы, конечно же, не ограничены этим. Только для другого примера, давайте рассмотрим «нормализацию» коллекции двойников. Мы хотим, чтобы найти самый большой один, а затем разделить каждое значение в коллекции с помощью этого, так что каждый элемент находится в диапазоне от 0,0 до 1,0, но все сохраняя те же отношения друг к другу, как они ранее имели:
double largest = * std::max_element(begin, end);
std::for_each(begin, end, [largest](double d) { return d/largest; });
Здесь снова мы имеем примерно такую же структуру: создаем объект функции, который хранит какое-то релевантное состояние, а затем повторно применяем этот объект функции operator()
для выполнения реальной работы.
- Мы могли бы разделить на меньше или равно, и больше, чем вместо этого. Или мы могли бы создать три группы: меньше, чем, больше, больше. Последнее может повысить эффективность при наличии многих дубликатов, но на данный момент нам действительно все равно.
- Намного больше узнать о лямбда-выражениях, чем просто это - я упрощаю некоторые вещи и полностью игнорирую других, которых мы сейчас не заботимся.
Не уверен, что согласен с закрытым голосованием. Это законный вопрос программирования. – par
Прочитайте [здесь] (http://stackoverflow.com/questions/356950/c-functors-and-their-uses) – adam10603
Это недостаток C++; не все языки страдают от этого. – molbdnilo