Menu
Coddy logo textTech

std::function и std::bind

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 86 из 104.

Хотя лямбда-выражения мощны, иногда возникает необходимость хранить вызываемые объекты разных типов единообразным способом или адаптировать существующие функции под требуемую сигнатуру. std::function и std::bind из заголовочного файла <functional> решают эти задачи.

std::function — это обертка с удалением типов (type-erased wrapper), которая может хранить любой вызываемый объект, соответствующий определенной сигнатуре: функции, лямбда-выражения или функциональные объекты:

#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 создает новый вызываемый объект, фиксируя некоторые аргументы существующей функции. Используйте std::placeholders::_1, _2 и т. д., чтобы обозначить аргументы, которые остаются переменными:

#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");  // Hello, Alice!
    
    auto swapped = std::bind(greet, _2, _1);
    swapped("Bob", "Hi");  // Hi, Bob!
}

Эти инструменты особенно полезны в ООП для хранения обратных вызовов в качестве членов класса или реализации паттерна «Стратегия». Однако современный C++ часто отдает предпочтение лямбда-выражениям вместо std::bind для лучшей читаемости и производительности — используйте std::bind в основном тогда, когда вам нужно изменить порядок аргументов или работать с устаревшим кодом.

challenge icon

Задание

Легко

Давайте создадим настраиваемый калькулятор, который демонстрирует возможности std::function и std::bind. Вы создадите систему, в которой математические операции можно сохранять, заменять и настраивать во время выполнения программы, показывая, как эти инструменты обеспечивают гибкое управление обратными вызовами (callbacks).

Вы организуете свой код в трех файлах:

  • MathOperations.h: Определите коллекцию автономных математических функций, которые будут служить вашей библиотекой операций.

    Создайте следующие функции:

    • add(int a, int b) — возвращает сумму
    • subtract(int a, int b) — возвращает разность (a - b)
    • multiply(int a, int b) — возвращает произведение
    • power(int base, int exponent, int multiplier) — возвращает multiplier * (base ^ exponent). Используйте простой цикл для вычисления степени (предполагается, что показатели степени неотрицательны).
  • Calculator.h: Определите класс Calculator, который использует std::function для динамического хранения и выполнения операций.

    Ваш Calculator должен иметь:

    • Приватный член std::function<int(int, int)> для хранения текущей бинарной операции
    • setOperation(std::function<int(int, int)> op) — устанавливает текущую операцию
    • calculate(int a, int b) — выполняет сохраненную операцию и возвращает результат

    Включите необходимые заголовки (<functional>) и обязательно подключите заголовочный файл MathOperations.

  • main.cpp: Считайте три входных значения:
    1. Название операции: add, subtract, multiply или square
    2. Первое число (целое)
    3. Второе число (целое)

    Создайте объект Calculator и настройте его в зависимости от названия операции:

    • Для add, subtract и multiply: назначьте соответствующую функцию напрямую калькулятору
    • Для square: используйте std::bind, чтобы создать специализированную версию power, которая всегда использует показатель степени 2 и множитель 1. Привязанная функция должна принимать два аргумента, где только первый используется в качестве основания (второй аргумент можно игнорировать с помощью плейсхолдера).

    После установки операции вызовите calculate() с вашими двумя числами и выведите:

    Result: [value]

    Затем продемонстрируйте замену операций, назначив калькулятору лямбда-выражение, которое возвращает a + b + 100, запустите его с теми же входными данными и выведите:

    With bonus: [value]

Например, при входных данных add, 10 и 5:

Result: 15
With bonus: 115

При входных данных multiply, 7 и 3:

Result: 21
With bonus: 110

При входных данных square, 4 и 0:

Result: 16
With bonus: 104

Эта задача показывает, как std::function обеспечивает единый способ хранения различных вызываемых типов (обычных функций, привязанных функций и лямбда-выражений), в то время как std::bind позволяет адаптировать существующие функции, фиксируя некоторые из их аргументов.

Шпаргалка

Заголовочный файл <functional> предоставляет std::function и std::bind для гибкой работы с вызываемыми объектами.

std::function — это обертка со стиранием типов, которая может хранить любой вызываемый объект, соответствующий определенной сигнатуре:

#include <functional>

std::function<int(int, int)> operation;

operation = add;                              // Обычная функция
operation = [](int a, int b) { return a * b; }; // Лямбда

std::bind создает новый вызываемый объект, фиксируя некоторые аргументы существующей функции. Используйте std::placeholders::_1, _2 и т. д. для переменных аргументов:

#include <functional>

void greet(const std::string& greeting, const std::string& name) {
    std::cout << greeting << ", " << name << "!\n";
}

using namespace std::placeholders;

auto sayHello = std::bind(greet, "Hello", _1);  // Фиксация первого аргумента
sayHello("Alice");  // Hello, Alice!

auto swapped = std::bind(greet, _2, _1);  // Изменение порядка аргументов
swapped("Bob", "Hi");  // Hi, Bob!

Эти инструменты полезны для хранения обратных вызовов в качестве членов класса или реализации гибких систем обратных вызовов. В современном C++ для лучшей читаемости часто предпочитают лямбда-выражения вместо 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
    //   Hint: Используйте 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;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование