第 18.13 節

std::bind

0瀏覽次數0訪問次數--跳出率--平均停留

本節解決什麼問題

有時你已經有一個函數,但它的參數和目標接口不匹配:

  • 想提前固定某些參數。
  • 想調整參數順序。
  • 想把成員函數綁定到某個對象上,變成普通回調。

std::bind 可以把已有可調用對象適配成新的可調用對象。

現代 C++ 中,很多 bind 場景可以用 Lambda 寫得更清楚。不過 std::bind 在舊項目、ROS2 示例、Boost.Asio 回調、成員函數適配中仍然經常出現,值得掌握。

這個特性是什麼

std::bind 接收一個可調用對象和一組綁定參數,返回一個新的可調用對象。調用新對象時,佔位符 _1_2_3 表示"調用時傳進來的第 1、2、3 個參數"。

std::bind 需要頭文件 <functional>,佔位符在命名空間 std::placeholders 中。

寫法含義
std::bind(f, 10, _1)固定 f 的第一個參數為 10
std::bind(f, _2, _1)調換調用時兩個參數的順序
std::bind(&C::m, &obj, _1)把成員函數綁定到對象 obj
std::bind(f, std::ref(x), _1)綁定引用,而不是拷貝

示例代碼

示例 1:固定普通函數的部分參數

#include <functional>
#include <iostream>
#include <string>

void print_student(const std::string& name, int age, int score)
{
    std::cout << name << " age=" << age << " score=" << score << "\n";
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    using std::placeholders::_1;
    using std::placeholders::_2;

    print_student("Alice", 20, 95);

    // bind 会把函数和部分参数提前绑定成一个可调用对象。
    auto print_bob = std::bind(print_student, "Bob", _1, _2);
    print_bob(21, 88);

    auto print_charlie_score = std::bind(print_student, "Charlie", 22, _1);
    print_charlie_score(91);

    return 0;
}

運行結果

Alice age=20 score=95
Bob age=21 score=88
Charlie age=22 score=91

示例 2:調整參數順序

佔位符的位置決定了原函數接收到的參數順序。

#include <functional>
#include <iostream>

void divide(int a, int b)
{
    std::cout << a << " / " << b << " = " << (a / b) << "\n";
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    using std::placeholders::_1;
    using std::placeholders::_2;

    divide(10, 2);

    // bind 会把函数和部分参数提前绑定成一个可调用对象。
    auto swapped = std::bind(divide, _2, _1);
    swapped(2, 10);

    auto half = std::bind(divide, _1, 2);
    half(10);
    half(20);

    return 0;
}

運行結果

10 / 2 = 5
10 / 2 = 5
10 / 2 = 5
20 / 2 = 10

示例 3:綁定成員函數

非靜態成員函數需要對象才能調用。std::bind 的第二個參數通常就是對象指針、對象引用或智能指針。

#include <functional>
#include <iostream>
#include <string>

class Printer
{
    std::string prefix_;

public:
    explicit Printer(const std::string& prefix) : prefix_(prefix) {}

    void print(int id, const std::string& text) const
    {
        std::cout << prefix_ << " #" << id << ": " << text << "\n";
    }
};

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    using std::placeholders::_1;
    using std::placeholders::_2;

    Printer printer("LOG");

    // bind 会把函数和部分参数提前绑定成一个可调用对象。
    auto print_any = std::bind(&Printer::print, &printer, _1, _2);
    print_any(7, "ready");

    auto print_ok = std::bind(&Printer::print, &printer, _1, "ok");
    print_ok(8);

    return 0;
}

運行結果

LOG #7: ready
LOG #8: ok

這裏 &printer 必須在 print_anyprint_ok 使用期間一直有效。如果綁定後的回調會被保存到更久的地方,通常要考慮智能指針或更明確的生命週期管理。

示例 4:bind 和 Lambda 的對比

同一個參數適配任務,Lambda 往往更直觀,因為參數名和調用關係都寫得很清楚。

#include <functional>
#include <iostream>

int weighted_sum(int base, int x, int y)
{
    return base + x * 10 + y;
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    using std::placeholders::_1;
    using std::placeholders::_2;

    // bind 会把函数和部分参数提前绑定成一个可调用对象。
    auto by_bind = std::bind(weighted_sum, 100, _1, _2);
    auto by_lambda = [](int x, int y) {
        return weighted_sum(100, x, y);
    };

    std::cout << "bind result = " << by_bind(3, 4) << "\n";
    std::cout << "lambda result = " << by_lambda(3, 4) << "\n";

    auto swapped_bind = std::bind(weighted_sum, 100, _2, _1);
    auto swapped_lambda = [](int x, int y) {
        return weighted_sum(100, y, x);
    };

    std::cout << "swapped bind result = " << swapped_bind(3, 4) << "\n";
    std::cout << "swapped lambda result = " << swapped_lambda(3, 4) << "\n";

    return 0;
}

運行結果

bind result = 134
lambda result = 134
swapped bind result = 143
swapped lambda result = 143

示例 5:bind 默認拷貝參數,需要引用時用 std::ref

std::bind 保存普通參數時默認拷貝。如果你要綁定引用,必須顯式使用 std::refstd::cref

#include <functional>
#include <iostream>

void add_to(int& total, int value)
{
    total += value;
    std::cout << "inside total = " << total << "\n";
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    int total = 0;

    // bind 会把函数和部分参数提前绑定成一个可调用对象。
    auto add_copy = std::bind(add_to, total, 5);
    add_copy();
    std::cout << "outside after copy bind = " << total << "\n";

    auto add_ref = std::bind(add_to, std::ref(total), 5);
    add_ref();
    std::cout << "outside after ref bind = " << total << "\n";

    return 0;
}

運行結果

inside total = 5
outside after copy bind = 0
inside total = 5
outside after ref bind = 5

關鍵語法解釋

寫法説明注意事項
_1調用新函數時傳入的第一個參數來自 std::placeholders
_2調用新函數時傳入的第二個參數可以用來調整順序
固定值直接寫在 bind 參數裏默認會被拷貝保存
&Class::method成員函數指針後面還要綁定對象
std::ref(x)按引用綁定 x不寫 std::ref 通常會拷貝

bind 和 Lambda 怎麼選

場景推薦
簡單固定參數Lambda 或 bind 都可以
參數關係需要讀得很清楚Lambda
綁定成員函數給舊接口bind 很常見,也可以用 Lambda
需要複雜邏輯、條件判斷Lambda
閲讀舊代碼或 ROS2/Boost.Asio 示例必須看懂 bind

一句話:新代碼優先考慮 Lambda;遇到已有接口適配、舊項目代碼、成員函數回調時,要能熟練讀懂 std::bind

常見錯誤

  1. 忘記佔位符來自 std::placeholders
  2. _1_2 的順序看反,導致實參順序錯誤。
  3. 綁定成員函數時忘記綁定對象。
  4. 綁定對象指針後,對象先被銷燬,回調還在使用。
  5. 以為 bind 默認按引用保存參數。默認是拷貝,需要引用時用 std::ref

使用建議

  1. 新代碼中,參數適配優先嚐試 Lambda。
  2. 讀 ROS2、Boost.Asio、舊項目代碼時,要能看懂 std::bind(&Class::method, this, _1, _2)
  3. 綁定成員函數時最先考慮對象生命週期。
  4. 需要引用語義時明確寫 std::refstd::cref
  5. 不要為了使用 bind 寫出難讀的佔位符組合,清晰比炫技重要。

小結

  • std::bind 用來固定參數、調整參數順序、綁定成員函數。
  • _1_2 表示調用新可調用對象時傳入的參數位置。
  • bind 默認拷貝綁定參數,需要引用時用 std::ref
  • 現代 C++ 中很多 bind 用法可以用 Lambda 替代。
  • 理解 bind 對閲讀 ROS2、Boost.Asio 和舊 C++ 項目很有幫助。
音乐页