Menu
CoddyTech

std::function 与 std::bind

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 86 课,共 104 课。

虽然 lambda 功能强大,但有时你需要以统一的方式存储不同类型的可调用对象,或者调整现有函数,使其符合所需的签名。std::function 和 std::bind(来自 <functional> 头文件)可以解决这些问题。

std::function 是一种类型擦除包装器,可以容纳任何符合特定签名的可调用对象,例如函数、lambda 或函数对象:

#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; }; // Lambda 表达式
    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!
}

这些工具在 OOP 中尤其有用,可用于将回调存储为类成员,或实现 Strategy 模式。不过,现代 C++ 通常更倾向于使用 lambda,而不是 std::bind,因为 lambda 的可读性和性能更好。只有在需要重新排列参数或处理旧代码时,才主要使用 std::bind。

challenge icon

挑战

简单

让我们构建一个可配置的计算器,展示 std::function 和 std::bind 的强大功能。你将创建一个系统,可以在运行时存储、切换和自定义数学运算,了解这些工具如何实现灵活的回调管理。

你将把代码放在 3 个文件中:

  • 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:读取 3 个输入:
    1. 运算名称:add、subtract、multiply 或 square
    2. 第一个数字(整数)
    3. 第二个数字(整数)

    创建一个 Calculator,并根据运算名称进行配置:

    • 对于 add、subtract 和 multiply:将对应的函数直接赋给计算器
    • 对于 square:使用 std::bind 创建 power 的专用版本,让它始终使用指数 2 和乘数 1。绑定后的函数应接受两个参数,其中只有第一个参数用作底数(可以使用占位符忽略第二个参数)。

    设置运算后,使用两个数字调用 calculate() 并打印:

    Result: [value]

    然后,将一个返回 a + b + 100 的 lambda 赋给计算器,以此演示切换运算。使用相同的输入运行它,并打印:

    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 如何提供统一的方式来存储不同类型的可调用对象(普通函数、绑定函数和 lambda),而 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") {
        // 在这里编写代码:结合 power 函数使用 std::bind
    }
    
    // TODO: 调用 calculate() 并打印结果
    // 格式:"Result: [value]"
    
    // TODO: 演示如何切换运算
    // 给计算器赋值一个返回 a + b + 100 的 lambda 函数
    // 再次调用 calculate() 并打印结果
    // 格式:"With bonus: [value]"
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器