2014-10-05 5 views
3

Я изучил всю идею функторов, к сожалению, я не могу понять реальное преимущество функторов над типичными функциями.Преимущество функтора C++ - удерживание состояния

Согласно некоторым академическим сценариям, функторы могут содержать состояния, отличные от функций. Может ли кто-нибудь продумать этот пример с помощью простого, простого для понимания примера?

Я действительно не могу понять, почему типичная, регулярная функция не в состоянии сделать то же самое. Мне очень жаль этот вопрос для начинающих.

+0

Не уверен, что согласен с закрытым голосованием. Это законный вопрос программирования. – par

+0

Прочитайте [здесь] (http://stackoverflow.com/questions/356950/c-functors-and-their-uses) – adam10603

+0

Это недостаток C++; не все языки страдают от этого. – molbdnilo

ответ

2

Как действительно тривиальная демонстрация, давайте рассмотрим быструю сортировку. Мы выбираем значение (обычно называемое «осью поворота») и разделяем входную коллекцию на те, которые сравниваются меньше, чем точка поворота, и те, которые сравниваются больше или равны с осью .

В стандартной библиотеке уже есть 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() для выполнения реальной работы.


  1. Мы могли бы разделить на меньше или равно, и больше, чем вместо этого. Или мы могли бы создать три группы: меньше, чем, больше, больше. Последнее может повысить эффективность при наличии многих дубликатов, но на данный момент нам действительно все равно.
  2. Намного больше узнать о лямбда-выражениях, чем просто это - я упрощаю некоторые вещи и полностью игнорирую других, которых мы сейчас не заботимся.
Смежные вопросы