| 技術 | 用途 |
| `std::tuple` | 保存可變數量、異構類型的綁定參數 |
| 整數序列 (`IndexTuple`) | 將 tuple 編譯期展開為參數包 |
| 型別萃取 (`result_traits`) | 推導可呼叫物件返回類型 |
| 完美轉發 (`std::forward`) | 保持參數值類別 |
| SFINAE / `decltype` | 佔位符識別與參數選擇 |
簡易實現核心碼
// 1. 整數序列生成 (C++14 起可用 std::index_sequence)
template struct IndexTuple {};
template
struct MakeIndexes : MakeIndexes {};
template
struct MakeIndexes<0, Indexes...> { using type = IndexTuple; };
// 2. 佔位符類型
template struct Placeholder {};
Placeholder<1> _1; Placeholder<2> _2; /* ... _9 */
// 3. 返回類型萃取
template struct result_traits : result_traits {};
template struct result_traits { using type = R; };
template struct result_traits { using type = R; };
// 4. 參數選擇:佔位符取實參,否則取綁定參數
template
auto select(T&& val, Tuple&) -> T&& { return std::forward(val); }
template
auto select(Placeholder&, Tuple& tp) -> decltype(std::get(tp)) {
return std::get(tp); // 實參 tuple 以 0 為基礎,佔位符 _1 對應索引 0
}
// 5. 呼叫器:將 tuple 展開並轉發
template
auto invoke(F&& f, BoundTuple& bound, IndexTuple, CallArgs&&... call_args)
-> typename result_traits::type {
return std::forward(f)(
select(std::get(bound), std::forward_as_tuple(call_args...))...
);
}
// 6. Bind 包裝類別
template
class BindWrapper {
F f_;
std::tuple bound_args_;
public:
BindWrapper(F f, BoundArgs&&... args)
: f_(std::move(f)), bound_args_(std::forward(args)...) {}
template
auto operator()(CallArgs&&... args)
-> typename result_traits::type {
return invoke(f_, bound_args_,
typename MakeIndexes::type{},
std::forward(args)...);
}
};
// 7. bind 函數模板
template
BindWrapper Bind(F f, Args&&... args) {
return BindWrapper(std::move(f), std::forward(args)...);
}
測試驗證
void test_func(int a, int b, int c) {
printf("a=%d, b=%d, c=%d\n", a, b, c);
}
void test_bind() {
// 全佔位符
Bind(&test_func, _1, _2, _3)(1, 2, 3); // a=1,b=2,c=3
// 混合綁定:固定參數 + 佔位符
Bind(&test_func, 4, 5, _1)(6); // a=4,b=5,c=6
Bind(&test_func, _1, 4, 5)(3); // a=3,b=4,c=5
Bind(&test_func, 3, _1, 5)(4); // a=3,b=4,c=5
// Lambda 綁定
auto f = Bind([](int x, int y){ return x + y; }, _1, 10);
printf("%d\n", f(5)); // 15
}
實現細節深度剖析
1. Tuple 展開機制
`MakeIndexes` 遞迴生成 `IndexTuple<0,1,2,...,N-1>`,`invoke` 利用參數包展開:
// 伪代码展開過程
invoke(f, bound, IndexTuple<0,1,2>, args...)
// 變為
f( select(bound[0], args_tuple), select(bound[1], args_tuple), select(bound[2], args_tuple) )
2. 佔位符順序語意
佔位符 `_N` 對應第 N 個實參 (1-based),《C++ 標準》規定:
- 實參少於佔位符最大編號:未使用的佔位符對應參數被忽略
- 實參多於佔位符:多餘實參被忽略
- 同一佔位符可重複出現:對應同一實參
3. 完美轉發保持值類別
void func(int&, int&&);
int x = 1;
Bind(&func, _1, _2)(x, 2);
// _1 選擇 x (lvalue) → int&
// _2 選擇 2 (rvalue) → int&&
// 完美轉發保證 func 接收正確引用類型
標準庫實現與簡易版差距
| 特性 | 簡易版 | std::bind (MSVC/libstdc++/libc++) |
| 成員函數指標支援 | ❌ | ✅ `&Class::mem_fn` |
| 成員資料指標支援 | ❌ | ✅ `&Class::data_member` |
| `std::reference_wrapper` | ❌ | ✅ `std::ref`/`std::cref` 強制引用綁定 |
| 巢狀 bind | ❌ | ✅ `bind(bind(f, _1), _2)` 正確遞迴展開 |
| noexcept 推導 | ❌ | ✅ C++17 起支援 |
| 編譯期錯誤訊息 | 難讀 | 概念/靜態斷言優化 |
現代 C++ 替代方案:Lambda 表達式
C++14 起,Lambda 幾乎完全取代 `bind`:
// bind 寫法
auto f1 = std::bind(&func, _1, 42, _2);
// Lambda 寫法 (更清晰、編譯器優化更好、除錯資訊更豐富)
auto f2 = [](auto a, auto b) { return func(a, 42, b); };
// 捕獲移動語意物件
auto f3 = [ptr = std::make_unique()](int x) { return ptr->process(x); };
建議:新代碼優先使用 Lambda;維護舊代碼或需動態組合可呼叫物件時使用 `std::bind`。
延伸閱讀
- 《C++ Templates: The Complete Guide》Chapter 17 - Bind
- MSVC STL 實現:`` 中 `_Bind`、`_Pmf_wrapper` 等
- null 同學圖文並茂解析:bind 實現過程圖解
- 上一篇文章: 基於原始套接字的網路封包嗅探器實作與混雜模式深度解析
- 下一篇文章: 快速排序 C++ 完整實現:從演算法原理到工程化代碼
Copyright ©
程式碼之家 All Rights Reserved