C++ bind 函数适配器

黎 浩然/ 7 12 月, 2023/ C/C++, 计算机/COMPUTER/ 0 comments

std::bind 是 C++ 标准库 <functional> 中提供的一个非常有用的函数适配器。它的主要作用是将一个可调用对象(如函数、成员函数、函数对象或lambda表达式)与其部分或全部参数进行“绑定”,从而生成一个新的、参数更少或参数顺序不同的可调用对象(通常称为“绑定器”或“函数对象”)。

简单来说,std::bind 允许你:

  1. 预设参数值(参数绑定):你可以为一个函数的某些参数预先设定好固定的值。当你调用由 std::bind 生成的新函数对象时,这些预设的参数就会自动传递给原始函数。
  2. 调整参数顺序和数量:通过使用特殊的“占位符”(placeholders),你可以控制调用新函数对象时提供的参数如何映射到原始函数的参数,甚至可以忽略某些参数或重复使用某些参数。
  3. 绑定成员函数:可以将一个类的成员函数与一个特定的对象实例(或指向对象的指针/引用)绑定起来,使得调用生成的新函数对象时,就像是直接通过那个对象调用了其成员函数。

核心组件:

  • 可调用对象:作为 std::bind 的第一个参数,是你想要绑定的原始函数或对象。
  • 要绑定的参数:紧随可调用对象之后的参数,是你希望预设的值,或者是占位符。
  • 占位符 (Placeholders):如 std::placeholders::_1, std::placeholders::_2, std::placeholders::_3 等。它们定义在 std::placeholders 命名空间中(通常需要 using namespace std::placeholders; 或显式指定 std::placeholders::_1)。_1 表示由 std::bind 返回的新函数对象的第一个参数,_2 表示第二个,以此类推。

返回值:

std::bind 返回一个未指定类型的函数对象。你可以将这个返回的对象存储在 auto 变量中,或者如果知道其大致签名,也可以存储在 std::function 对象中。

主要使用场景和示例:

  • 为普通函数绑定参数:
#include <iostream>
#include <functional> // 为了 std::bind 和占位符

void print_sum(int a, int b, int c) {
    std::cout << a << " + " << b << " + " << c << " = " << (a + b + c) << std::endl;
}

int main() {
    using namespace std::placeholders; // 使得可以直接使用 _1, _2 等

    // 1. 绑定所有参数
    auto f1 = std::bind(print_sum, 10, 20, 30);
    f1(); // 输出: 10 + 20 + 30 = 60 (不需要再提供参数)

    // 2. 绑定部分参数,使用占位符
    auto f2 = std::bind(print_sum, 100, _1, 200); // _1 代表 f2 的第一个参数
    f2(50); // 输出: 100 + 50 + 200 = 350 (50 会被传递给 print_sum 的 b 参数)

    // 3. 改变参数顺序
    auto f3 = std::bind(print_sum, _2, _1, 300); // f3的第一个参数给b,第二个给a
    f3(50, 60); // 输出: 60 + 50 + 300 = 410
}
  • 为成员函数绑定参数:
#include <iostream>
#include <functional>
#include <string>

struct Greeter {
    Greeter(const std::string& id) : id_(id) {}
    void greet(const std::string& name, const std::string& punctuation) {
        std::cout << id_ << " says: Hello, " << name << punctuation << std::endl;
    }
    std::string id_;
};

int main() {
    using namespace std::placeholders;

    Greeter g1("Greeter#1");
    Greeter g2("Greeter#2");

    // 绑定成员函数需要传递对象实例(或指针/引用)作为第二个参数
    auto g1_greet_bob = std::bind(&Greeter::greet, &g1, "Bob", "!"); // 对象指针
    g1_greet_bob(); // 输出: Greeter#1 says: Hello, Bob!

    auto g2_greet_custom = std::bind(&Greeter::greet, g2, _1, "..."); // 对象(会拷贝g2)
    g2_greet_custom("Alice"); // 输出: Greeter#2 says: Hello, Alice...
                           // 注意:这里g2被拷贝到bind返回的函数对象中。
                           // 如果想用g2的引用,应使用 std::ref(g2) 或 &g2

    // 如果想在调用时指定称呼和标点
    auto any_greet_from_g1 = std::bind(&Greeter::greet, &g1, _1, _2);
    any_greet_from_g1("Charlie", "??"); // Greeter#1 says: Hello, Charlie??
}
  • 适配函数签名以用于STL算法等: 有时STL算法(如 std::for_each, std::count_if)需要特定签名的可调用对象,而你现有的函数签名不匹配。std::bind 可以用来适配。
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

bool is_greater_than(int value, int threshold) {
    return value > threshold;
}

int main() {
    using namespace std::placeholders;
    std::vector<int> numbers = {10, 25, 5, 40, 15};
    int threshold = 20;

    // std::count_if 需要一个接收单个参数并返回bool的函数
    // is_greater_than 接收两个参数,我们可以用 bind 固定第二个参数
    int count = std::count_if(numbers.begin(), numbers.end(),
                              std::bind(is_greater_than, _1, threshold));
    // _1 会依次接收 numbers 中的每个元素
    // threshold (20) 会作为 is_greater_than 的第二个参数

    std::cout << "Numbers greater than " << threshold << ": " << count << std::endl; // 输出: 2
}

参数的传递方式(拷贝 vs. 引用):

  • 默认情况下,std::bind 会拷贝它绑定的参数(除了占位符之外的那些值)。
  • 如果你希望绑定一个对象的引用而不是拷贝,你需要使用 std::ref 或 std::cref(类似于 std::thread 的参数传递)。
std::string message = "Original";
// 错误:binder_ref 内部会有一个 message 的拷贝
// auto binder_copy = std::bind(some_func_expecting_string_ref, message);
// 正确:binder_ref 内部会有一个对 message 的引用
auto binder_ref = std::bind(some_func_expecting_string_ref, std::ref(message));

与Lambda表达式的关系:

在现代C++ (C++11及以后),很多 std::bind 的使用场景可以用lambda表达式更简洁、更直观地实现。Lambda表达式通常具有更好的可读性,并且在某些情况下可以避免 std::bind 的一些复杂性或潜在的性能问题。

例如,上面的 std::count_if 例子可以用lambda改写为:

int count = std::count_if(numbers.begin(), numbers.end(),
                          [threshold](int value) { return value > threshold; });

尽管如此,std::bind 仍然是标准库的一部分,并且在某些特定情况下(比如需要非常灵活的参数重排,或者处理函数指针和成员函数指针时)仍然有其用武之地。

总而言之,std::bind 是一个强大的工具,用于创建新的可调用对象,通过固定、重排或提供参数占位符来适配现有的函数和成员函数。

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*