在 Cocos2d-x 等現代 C++ 框架原始碼中,頻繁出現 std::bind 定義的回調宏。本文從 C++98 的 bind1st/bind2nd 切入,層層遞進講解 C++11 std::bind 的核心機制、佔位符用法、成員函數綁定、Lambda 協作模式,並對比 std::function 協同工作流程,助你徹底掌握現代 C++ 可調用物件綁定技術。
std::bind1st 與 std::bind2nd 是 C++98 標準庫提供的雙參數仿函數參數綁定工具,定義於 <functional>:
bind1st(op, val):將二元運算 op 的**第一個參數**綁定為 val,返回一元仿函數bind2nd(op, val):將二元運算 op 的**第二個參數**綁定為 val,返回一元仿函數#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
vector<int> coll;
for (int i = 1; i <= 10; ++i) coll.push_back(i);
// less<int>() 是二元謂詞:less(a, b) 等價於 a < b
// bind1st(less<int>(), 10) → 綁定第一參數=10 → 判斷 10 < elem
int res = count_if(coll.begin(), coll.end(), bind1st(less<int>(), 10));
cout << "大於 10 的個數: " << res << endl; // 0
// bind2nd(less<int>(), 10) → 綁定第二參數=10 → 判斷 elem < 10
res = count_if(coll.begin(), coll.end(), bind2nd(less<int>(), 10));
cout << "小於 10 的個數: " << res << endl; // 9
return 0;
}
std::bind 將可調用物件與部分參數預先綁定,生成新的可調用物件,實現**參數綁定**、**參數重排**、**參數佔位**三大核心能力。其簽名:
template<class F, class... Args>
auto bind(F&& f, Args&&... args) -> /* 未指定類型 */;
std::placeholders::_1, _2, _3... 代表新可調用物件的第 1、2、3... 個參數std::ref/std::cref 引用包裹std::function#include <iostream>
#include <functional>
using namespace std;
int TestFunc(int a, char c, float f) {
cout << "a=" << a << ", c=" << c << ", f=" << f << endl;
return a;
}
int main() {
// 綁定第 1 參數佔位,第 2 參數固定 'A',第 3 參數固定 100.1f
auto bindFunc1 = bind(TestFunc, placeholders::_1, 'A', 100.1f);
bindFunc1(10); // 調用 TestFunc(10, 'A', 100.1f)
cout << "=================================\n";
// 參數重排:新第 1 參數→原第 2,新第 2 參數→原第 1,第 3 固定
auto bindFunc2 = bind(TestFunc, placeholders::_2, placeholders::_1, 100.1f);
bindFunc2('B', 20); // 調用 TestFunc(20, 'B', 100.1f)
cout << "=================================\n";
// 完全重排:新參數順序 (3, 1, 2) 映射到原 (1, 2, 3)
auto bindFunc3 = bind(TestFunc, placeholders::_3, placeholders::_1, placeholders::_2);
bindFunc3(100.1f, 30, 'C'); // 調用 TestFunc(30, 'C', 100.1f)
return 0;
}
| bind 參數位置 | 佔位符 | 映射至調用時第幾個參數 |
|---|---|---|
| 第 1 個 (callable 後) | _1 | 第 1 個 |
| 第 2 個 | _2 | 第 2 個 |
| 第 N 個 | _N | 第 N 個 |
口訣:bind(f, _2, _1) 表示「調用時的第 2 參數給 f 的第 1 參數,調用時的第 1 參數給 f 的第 2 參數」。
#include <iostream>
#include <functional>
using namespace std;
class View {
public:
void onClick(int x, int y) {
cout << "X: " << x << ", Y: " << y << endl;
}
};
int main() {
View button;
// 綁定成員函數需傳遞物件指標/引用作為第一參數
auto callback = bind(&View::onClick, &button, placeholders::_1, placeholders::_2);
callback(100, 200); // 等價 button.onClick(100, 200)
// 配合 std::function 統一介面
function<void(int, int)> clickCallback = bind(&View::onClick, &button, _1, _2);
clickCallback(300, 400);
return 0;
}
auto sp = make_shared<View>();
auto cb = bind(&View::onClick, sp, _1, _2); // shared_ptr 可直接綁定
View obj;
auto cb_ref = bind(&View::onClick, ref(obj), _1, _2); // 引用包裹,延長生命週期
class Base { virtual void foo(int) { cout << "Base\n"; } };
class Derived : public Base { void foo(int) override { cout << "Derived\n"; } };
Derived d;
Base* ptr = &d;
auto cb = bind(&Base::foo, ptr, _1); // 動態多態生效,調用 Derived::foo
cb(42);
| 特性 | 說明 | 最佳實踐 |
|---|---|---|
| 參數傳遞語義 | 預先綁定參數以**值傳遞**儲存(拷貝/移動構造) | 大型物件用 std::ref/std::cref 或智慧指標 |
| 佔位符參數 | 以**引用傳遞**轉發給目標函數 | 注意引用生命週期,避免懸空引用 |
| 返回類型 | 未指定類型的可調用物件,可隱式轉換 std::function | 明確儲存時用 auto 或 std::function<Sig> |
| 巢狀 bind | 支援 bind(bind(f, _1), _2) 等嵌套 | 複雜場景優先考慮 Lambda 表達式 |
| this 指標綁定 | 可用 this、&obj、shared_ptr、unique_ptr | 優先用智慧指標管理生命週期 |
| 例外傳播 | 目標函數拋出的例外直接透傳 | 在回調鏈中統一處理例外 |
| 維度 | std::bind | Lambda |
|---|---|---|
| 語法簡潔性 | 冗長,需 placeholders | 簡潔直觀 [cap](params){body} |
| 參數重排 | 原生支援 | 需手動調整參數順序 |
| 編譯期效能 | 模板實例化開銷大 | 通常更優 |
| 可讀性 | 複雜綁定難閱讀 | 邏輯集中易維護 |
| 適用場景 | 成員函數指標、現有函數適配、框架回調註冊 | 臨時邏輯、複雜捕獲、泛型 Lambda |