第 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_any 和 print_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::ref 或 std::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。
常見錯誤
- 忘記佔位符來自
std::placeholders。 - 把
_1、_2的順序看反,導致實參順序錯誤。 - 綁定成員函數時忘記綁定對象。
- 綁定對象指針後,對象先被銷燬,回調還在使用。
- 以為
bind默認按引用保存參數。默認是拷貝,需要引用時用std::ref。
使用建議
- 新代碼中,參數適配優先嚐試 Lambda。
- 讀 ROS2、Boost.Asio、舊項目代碼時,要能看懂
std::bind(&Class::method, this, _1, _2)。 - 綁定成員函數時最先考慮對象生命週期。
- 需要引用語義時明確寫
std::ref或std::cref。 - 不要為了使用
bind寫出難讀的佔位符組合,清晰比炫技重要。
小結
std::bind用來固定參數、調整參數順序、綁定成員函數。_1、_2表示調用新可調用對象時傳入的參數位置。bind默認拷貝綁定參數,需要引用時用std::ref。- 現代 C++ 中很多
bind用法可以用 Lambda 替代。 - 理解
bind對閲讀 ROS2、Boost.Asio 和舊 C++ 項目很有幫助。