Ответ
Функтор (или объект-функция) в 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; }
};
Так что функторы — это не какая-то древняя пиздопроебибна. Это фундамент. Лямбды — их младшие, но удобные братья. А сам подход — мощный и живой, особенно когда тебе нужен явно именованный тип или сложная логика инициализации.