Что такое функтор (Functor) в C++?

Ответ

Функтор (или объект-функция) в C++ — это объект класса, для которого перегружен оператор вызова operator(). Это позволяет использовать объект так, как будто он является функцией.

Простой пример:

struct Multiplier {
    int factor;
    Multiplier(int f) : factor(f) {} // Конструктор сохраняет состояние
    // Перегрузка оператора вызова функции
    int operator()(int x) const {
        return x * factor;
    }
};

int main() {
    Multiplier multiplyBy5(5);
    Multiplier multiplyBy3(3);

    std::cout << multiplyBy5(10); // Выведет 50. Вызов obj(10) -> obj.operator()(10)
    std::cout << multiplyBy3(7);  // Выведет 21

    // Функторы легко работают с алгоритмами STL
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::transform(vec.begin(), vec.end(), vec.begin(), multiplyBy5);
    // vec теперь содержит {5, 10, 15, 20, 25}
}

Ключевые преимущества функторов перед указателями на функции:

  • Сохранение состояния: Функтор может иметь поля-члены (как factor в примере), что делает его более гибким.
  • Возможность инлайнинга: Компилятор может легко встроить вызов operator(), что часто приводит к более эффективному коду по сравнению с вызовом через указатель на функцию.
  • Полиморфизм: Функтор может быть полиморфным, используя виртуальные методы в operator().

Связь с лямбда-выражениями: Начиная с C++11, лямбда-выражения — это синтаксический сахар для анонимных функторов. Компилятор преобразует лямбду в уникальный класс с operator().

// Эта лямбда...
auto lambda = [factor=5](int x) { return x * factor; };
// ...примерно эквивалентна такому функтору:
struct __AnonymousLambda {
    int factor;
    int operator()(int x) const { return x * factor; }
};

Функторы остаются полезными, когда нужна сложная инициализация, наследование или когда тип должен быть явно именован.

Ответ 18+ 🔞

А, функторы! Ну, это ж классика, ёпта. Представь себе такую хуйню: обычный объект, но с приколом — у него перегружен оператор вызова, эти самые круглые скобочки operator(). И получается, что ты этот объект используешь ровно как функцию, вызываешь его. Хуй с горы, а не технология.

Вот смотри, простейший пример, чтоб не ебать мозг:

struct Multiplier {
    int factor;
    Multiplier(int f) : factor(f) {} // Конструктор сохраняет состояние
    // А вот и магия — перегрузка оператора вызова
    int operator()(int x) const {
        return x * factor;
    }
};

int main() {
    Multiplier multiplyBy5(5); // Создал объект-умножатель на пять
    Multiplier multiplyBy3(3); // А это — на три

    std::cout << multiplyBy5(10); // Выведет 50. Смотри, вызываю объект! obj(10) -> obj.operator()(10)
    std::cout << multiplyBy3(7);  // Выведет 21

    // А ещё эти штуки овердохуища удобны с алгоритмами STL
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::transform(vec.begin(), vec.end(), vec.begin(), multiplyBy5);
    // vec теперь {5, 10, 15, 20, 25} — всё посчитал, красота
}

А теперь, блядь, главный вопрос: нахуя это всё, если есть обычные функции? А вот нахуя:

  • Они с памятью дружат. Функтор — он как хитрая жопа, может внутри себя состояние хранить (как тот самый factor). Указатель на функцию — тот ещё бздун, он такое не потянет без костылей.
  • Скорость, детка. Компилятор, когда видит вызов operator(), часто просто встраивает этот код прямо на место. Получается быстро, без лишних прыжков по адресам. С указателем на функцию — там доверия ебать ноль, компилятор редко рискнёт его инлайнить.
  • Могут быть умными. Можно сделать полиморфный функтор с виртуальными методами прямо в operator(). Это уже уровень "э бошка думай".

И причём тут лямбды, спросишь ты? А лямбды, чувак, — это просто сахар, ебушки-воробушки. Красивая обёртка для тех же самых функторов. Компилятор берёт твою лямбду и делает из неё анонимный класс ровно как в примере выше.

// Написал лямбду...
auto lambda = [factor=5](int x) { return x * factor; };
// А компилятор внутри себя матерится и генерит что-то типа:
struct __AnonymousLambda {
    int factor;
    int operator()(int x) const { return x * factor; }
};

Так что функторы — это не какая-то древняя пиздопроебибна. Это фундамент. Лямбды — их младшие, но удобные братья. А сам подход — мощный и живой, особенно когда тебе нужен явно именованный тип или сложная логика инициализации.