Wiki
std::bind
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
1.本節解決什麼問題
有時你已經有一個函式,但它的引數和目標介面不匹配:
- 想提前固定某些引數。
- 想調整引數順序。
- 想把成員函式繫結到某個物件上,變成普通回撥。
std::bind 可以把已有可呼叫物件適配成新的可呼叫物件。
現代 C++ 中,很多 bind 場景可以用 Lambda 寫得更清楚。不過 std::bind 在舊專案、ROS2 示例、Boost.Asio 回撥、成員函式適配中仍然經常出現,值得掌握。
2.這個特性是什麼
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) |
繫結引用,而不是複製 |
3.示例程式碼
3.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
3.2.示例 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.3.示例 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 使用期間一直有效。如果繫結後的回撥會被儲存到更久的地方,通常要考慮智慧指標或更明確的生命週期管理。
3.4.示例 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
3.5.示例 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
3.6.示例 6:在成員函式中使用 this 綁定當前物件
前面的示例是在類外部建立物件,再把物件地址 &printer 傳給 std::bind。如果 std::bind 本身就在類的成員函式中使用,可以直接傳入 this,表示把成員函式繫結到當前物件。
#include <functional>
#include <iostream>
#include <string>
class Robot
{
std::string name_;
public:
explicit Robot(const std::string& name) : name_(name) {}
void on_message(int id, const std::string& text)
{
std::cout << name_ << " #" << id << ": " << text << "\n";
}
void demo()
{
using std::placeholders::_1;
using std::placeholders::_2;
auto callback = std::bind(&Robot::on_message, this, _1, _2);
callback(7, "ready");
callback(8, "running");
}
};
int main()
{
Robot robot("Cyber");
robot.demo();
return 0;
}
執行結果:
Cyber #7: ready
Cyber #8: running
這裡最重要的是:
auto callback = std::bind(&Robot::on_message, this, _1, _2);
把它拆開看:
| 引數 | 含義 |
|---|---|
&Robot::on_message |
要呼叫的成員函式 |
this |
呼叫這個成員函式的當前物件 |
_1 |
callback() 呼叫時傳入的第一個引數 |
_2 |
callback() 呼叫時傳入的第二個引數 |
非靜態成員函式不能脫離物件單獨呼叫。&Robot::on_message 只說明瞭“要呼叫哪個成員函式”,還必須告訴 std::bind“在哪個 Robot 物件上呼叫”。
在類外部,可以寫:
std::bind(&Robot::on_message, &robot, _1, _2);
而在 Robot 自己的成員函式內部,this 就是當前物件的指標,所以可以直接寫:
std::bind(&Robot::on_message, this, _1, _2);
例如:
Robot robot("Cyber");
robot.demo();
進入 robot.demo() 後,demo() 內部的 this 就指向 robot。
因此:
callback(7, "ready");
可以近似理解成:
this->on_message(7, "ready");
如果當前物件就是 robot,也就相當於:
robot.on_message(7, "ready");
所以這類寫法可以記成:
std::bind(&类名::成员函数, this, 参数...);
其中第二個引數 this 的作用是:
告訴
std::bind:以後呼叫這個成員函式時,就在當前這個物件上呼叫。
這也是 ROS2 和 Boost.Asio 中很常見的寫法,例如:
std::bind(&MyNode::callback, this, _1)
它表示把 MyNode::callback 繫結到當前這個 MyNode 物件上,再把呼叫時的第一個引數傳給 callback。
需要注意,this 本質上是一個指標。繫結後的回撥如果活得比當前物件更久,物件銷燬後再呼叫回撥,就可能訪問已經失效的 this,因此非同步回撥中仍然要注意物件生命週期。
4.關鍵語法解釋
| 寫法 | 說明 | 注意事項 |
|---|---|---|
_1 |
呼叫新函式時傳入的第一個引數 | 來自 std::placeholders |
_2 |
呼叫新函式時傳入的第二個引數 | 可以用來調整順序 |
| 固定值 | 直接寫在 bind 引數裡 |
預設會被複製儲存 |
&Class::method |
成員函式指標 | 後面還要繫結物件 |
this |
成員函式內部的當前物件指標 | 常作為繫結成員函式時的第二個引數 |
std::ref(x) |
按引用繫結 x |
不寫 std::ref 通常會複製 |
5.bind 和 Lambda 怎麼選
| 場景 | 推薦 |
|---|---|
| 簡單固定引數 | Lambda 或 bind 都可以 |
| 引數關係需要讀得很清楚 | Lambda |
| 繫結成員函式給舊介面 | bind 很常見,也可以用 Lambda |
| 需要複雜邏輯、條件判斷 | Lambda |
| 閱讀舊程式碼或 ROS2/Boost.Asio 示例 | 必須看懂 bind |
一句話:新程式碼優先考慮 Lambda;遇到已有介面適配、舊專案程式碼、成員函式回撥時,要能熟練讀懂 std::bind。
6.常見錯誤
- 忘記佔位符來自
std::placeholders。 - 把
_1、_2的順序看反,導致實參順序錯誤。 - 繫結成員函式時忘記繫結物件。
- 繫結物件指標後,物件先被銷燬,回撥還在使用。
- 以為
bind預設按引用儲存引數。預設是複製,需要引用時用std::ref。
7.使用建議
- 新程式碼中,引數適配優先嚐試 Lambda。
- 讀 ROS2、Boost.Asio、舊專案程式碼時,要能看懂
std::bind(&Class::method, this, _1, _2)。 - 繫結成員函式時最先考慮物件生命週期。
- 需要引用語義時明確寫
std::ref或std::cref。 - 不要為了使用
bind寫出難讀的佔位符組合,清晰比炫技重要。
8.小結
std::bind用來固定引數、調整引數順序、繫結成員函式。_1、_2表示呼叫新可呼叫物件時傳入的引數位置。- 成員函式中可以把
this作為第二個引數,表示把成員函式繫結到當前物件。 bind預設複製繫結引數,需要引用時用std::ref。- 現代 C++ 中很多
bind用法可以用 Lambda 替代。 - 理解
bind對閱讀 ROS2、Boost.Asio 和舊 C++ 專案很有幫助。