std::function и std::bind
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 86 из 104.
Хотя лямбда-выражения обладают широкими возможностями, иногда необходимо хранить вызываемые объекты разных типов единообразным способом или адаптировать существующие функции для соответствия требуемой сигнатуре. std::function и std::bind из заголовочного файла <functional> решают эти задачи.
std::function — это оболочка со стёртым типом, которая может содержать любой вызываемый объект, соответствующий определённой сигнатуре, — функции, лямбды или функциональные объекты:
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
std::function<int(int, int)> operation;
operation = add; // Обычная функция
std::cout << operation(3, 4) << "\n"; // 7
operation = [](int a, int b) { return a * b; }; // Лямбда
std::cout << operation(3, 4) << "\n"; // 12
}std::bind создаёт новый вызываемый объект, фиксируя некоторые arguments существующей function. Используйте std::placeholders::_1, _2 и т. д., чтобы обозначить arguments, которые остаются переменными:
#include <iostream>
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
int main() {
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1);
sayHello("Alice"); // Привет, Алиса!
auto swapped = std::bind(greet, _2, _1);
swapped("Bob", "Hi"); // Привет, Боб!
}Эти инструменты особенно полезны в ООП для хранения обратных вызовов в качестве членов class или реализации паттерна Strategy. Однако в современном C++ часто предпочитают лямбды вместо std::bind благодаря лучшей читаемости и производительности — используйте std::bind главным образом, когда нужно изменить порядок аргументов или работать с устаревшим кодом.
Задание
ЛегкоДавайте создадим настраиваемый калькулятор, демонстрирующий возможности std::function и std::bind. Вы создадите систему, в которой математические операции можно хранить, заменять и настраивать во время выполнения, показывая, как эти инструменты обеспечивают гибкое управление callback-функциями.
Вы организуете код в трёх файлах:
MathOperations.h: Declare набор отдельных математических функций, которые будут служить библиотекой операций.Create эти функции:
add(int a, int b): возвращает суммуsubtract(int a, int b): возвращает difference (a - b)multiply(int a, int b): возвращает произведениеpower(int base, int exponent, int multiplier): возвращаетmultiplier * (base ^ exponent). Используйте простой цикл, чтобы calculate степень (assume неотрицательные exponents).
Calculator.h: DeclareCalculatorclass, который используетstd::functionдля динамического хранения и выполнения операций.Ваш Calculator должен содержать:
- Приватный член
std::function<int(int, int)>для хранения current бинарной операции setOperation(std::function<int(int, int)> op): устанавливает current операциюcalculate(int a, int b): Executes сохранённую операцию и возвращает результат
Include необходимые заголовочные файлы (
<functional>) и убедитесь, что вы подключили заголовочный файл MathOperations.- Приватный член
main.cpp: Считайте три inputs:- Название операции:
add,subtract,multiplyилиsquare - Первое число (целое)
- Второе число (целое)
Create
Calculatorи Configure его на основе названия операции:- Для
add,subtractиmultiply: assign соответствующую function непосредственно калькулятору - Для
square: используйтеstd::bind, чтобы создать специализированную версиюpower, которая always использует exponent 2 и multiplier 1. Связанная function должна принимать два аргумента, при этом только первый используется как base (второй аргумент можно пропустить с помощью заполнителя).
После установки операции вызовите
calculate()с двумя вашими числами и выведите:Result: [value]Затем продемонстрируйте замену операций, assign лямбда-функцию, которая возвращает
a + b + 100, калькулятору, выполните её с теми же inputs и выведите:With bonus: [value]- Название операции:
Например, с inputs add, 10 и 5:
Result: 15
With bonus: 115С inputs multiply, 7 и 3:
Result: 21
With bonus: 110С inputs square, 4 и 0:
Result: 16
With bonus: 104Это задание показывает, как std::function предоставляет единообразный способ хранить различные типы вызываемых объектов (обычные функции, связанные функции и лямбда-функции), тогда как std::bind позволяет адаптировать существующие функции, фиксируя некоторые из их аргументов.
Попробуйте сами
#include <iostream>
#include <string>
#include <functional>
#include "Calculator.h"
using namespace std;
int main() {
// Прочитать входные данные
string operation;
int num1, num2;
cin >> operation >> num1 >> num2;
// Создать экземпляр Calculator
Calculator calc;
// TODO: Настроить калькулятор на основе имени операции
// Для "add", "subtract", "multiply": назначить соответствующую функцию напрямую
// Для "square": использовать std::bind для создания специализированной версии power
// которая всегда использует показатель степени 2 и множитель 1
// Подсказка: Используйте std::placeholders::_1 для аргумента основания
if (operation == "add") {
// Ваш код здесь
} else if (operation == "subtract") {
// Ваш код здесь
} else if (operation == "multiply") {
// Ваш код здесь
} else if (operation == "square") {
// Ваш код здесь - используйте std::bind с функцией power
}
// TODO: Вызвать calculate() и вывести результат
// Формат: "Result: [value]"
// TODO: Продемонстрировать замену операций
// Присвойте калькулятору лямбду, которая возвращает a + b + 100
// Снова вызовите calculate() и выведите результат
// Формат: "With bonus: [value]"
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер12Возможности современного C++
Семантика перемещения и RvaluesИдеальная передача (Perfect Forwarding)Лямбда-выражения в деталяхstd::function и std::bindconstexpr и constevalСтруктурированные привязкиoptional, variant, anyПотренируйтесь самостоятельно: Онлайн-компилятор C++