Avatar ·

Что такое функторы в C++ и зачем они нужны?

Привет! Я изучаю C++ и наткнулся на термин "функтор" (functor). Читал, что это что-то вроде объекта, который ведет себя как функция. Но я не совсем понимаю, зачем это нужно, если есть обычные функции и указатели на функции. Например, я знаю, что можно передать функцию в `std::sort` как компаратор: ```cpp bool compare(int a, int b) { return a < b; } std::sort(vec.begin(), vec.end(), compare); ``` Но зачем использовать объект с перегруженным `operator()`? В чем преимущество? Я слышал, что функторы могут хранить состояние, но не понимаю, как это применить на практике. Можете объяснить на простом примере, где функтор реально удобнее обычной функции? И еще вопрос: функторы — это то же самое, что лямбда-выражения? Или это разные вещи? Буду благодарен за развернутое объяснение с примерами кода. <<>>

Что такое функтор?

Функтор (или функциональный объект) — это класс или структура, в которой перегружен оператор вызова функции operator(). Благодаря этому объект такого класса можно использовать так же, как обычную функцию:

class Adder {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};

Adder add; // создаем объект-функтор
int result = add(3, 4); // вызываем как функцию → result = 7

Почему функторы лучше обычных функций?

  1. Сохранение состояния. Функтор может хранить данные между вызовами. Обычная функция для этого использует глобальные или статические переменные — это небезопасно и неудобно.
  2. Производительность. Компилятор может легко встроить (inline) вызов функтора, так как тип объекта известен во время компиляции. С указателем на функцию это сложнее.
  3. Гибкость. Функтор может иметь несколько перегруженных версий operator().

Пример с состоянием

Допустим, нужно подсчитать, сколько раз компаратор вызывался при сортировке:

#include <iostream>
#include <vector>
#include <algorithm>

struct CountingComparator {
    int count = 0;

    bool operator()(int a, int b) {
        ++count;
        return a < b;
    }
};

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5};
    CountingComparator comp;
    std::sort(v.begin(), v.end(), comp);
    std::cout << "Comparisons: " << comp.count << std::endl; // выведет количество
    return 0;
}

Функторы и лямбды

Лямбда-выражения — это синтаксический сахар для создания безымянных функторов. Компилятор превращает лямбду в класс с перегруженным operator(). Например:

auto lambda = [](int a, int b) { return a < b; };
// Компилятор создает что-то вроде:
struct __lambda {
    bool operator()(int a, int b) const { return a < b; }
} lambda;

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

Практический пример: фильтрация по порогу

class ThresholdFilter {
    int threshold;
public:
    ThresholdFilter(int t) : threshold(t) {}
    bool operator()(int value) const {
        return value > threshold;
    }
};

// Использование:
std::vector<int> data = {1, 5, 8, 3, 10};
ThresholdFilter filter(5);
// Удаляем элементы, не проходящие фильтр
data.erase(std::remove_if(data.begin(), data.end(), filter), data.end());
// Останется: 8, 10

Итог

Функторы — мощный инструмент, который сочетает гибкость объектов и синтаксис функций. Они особенно полезны в алгоритмах STL, callback-системах и везде, где нужно сохранять контекст между вызовами. Лямбды — их современная альтернатива, но понимание функторов необходимо для глубокого понимания C++.

<<>>

Дополнение: функторы как стратегии (полиморфизм без виртуальных функций)

Еще одно важное применение функторов — реализация паттерна "Стратегия" с нулевыми накладными расходами. Вместо того чтобы использовать виртуальные функции (что замедляет код), можно параметризовать алгоритм функтором через шаблон:

template <typename Comparator>
void sort_and_print(std::vector<int>& v, Comparator comp) {
    std::sort(v.begin(), v.end(), comp);
    for (int x : v) std::cout << x << " ";
}

struct Ascending {
    bool operator()(int a, int b) const { return a < b; }
};

struct Descending {
    bool operator()(int a, int b) const { return a > b; }
};

// Использование:
std::vector<int> vec = {3, 1, 4, 1, 5};
sort_and_print(vec, Ascending{});  // 1 1 3 4 5
sort_and_print(vec, Descending{}); // 5 4 3 1 1

Компилятор создаст две разные версии функции sort_and_print — для каждого типа функтора. Это даёт максимальную производительность, так как вызов operator() будет встроен (inlined). С указателями на функцию или виртуальными методами такого не добиться.

Кроме того, функторы можно комбинировать. Например, создать функтор, который применяет несколько операций последовательно:

class Compose {
    std::function<int(int)> f, g;
public:
    Compose(std::function<int(int)> f, std::function<int(int)> g) : f(f), g(g) {}
    int operator()(int x) const { return f(g(x)); }
};

Это делает функторы отличным инструментом для функционального программирования в C++.