# 【C】C++ std::bind 函數綁定
## 簡介
在工作時使用開源專案 [**rpicam-apps**](https://github.com/raspberrypi/rpicam-apps) 來進行影像抓取,但由於我們需要將 image buffer 抓下來,需要修改原始程式碼來將資料放到 Share Memory Buffer,rpicam-apps 中有一段程式碼如下
[rpicam-apps/blob/main/apps/rpicam_vid.cpp](https://github.com/raspberrypi/rpicam-apps/blob/main/apps/rpicam_vid.cpp)
```cpp!
std::unique_ptr<Output> output = std::unique_ptr<Output>(Output::Create(options));
app.SetEncodeOutputReadyCallback(std::bind(&Output::OutputReady, output.get(), _1, _2, _3, _4));
app.SetMetadataReadyCallback(std::bind(&Output::MetadataReady, output.get(), _1));
```
首先 `App` 類別為應用層所設計的類別,負責輸出影像包含檔案、影片或串流,所以在 Terminal 下指令的時候就會依照對應的 `App` 執行檔案執行。
`SetEncodeOutputReadyCallback` 為一個 Callback 函數設定,代表著在影像 encode 完後要怎麼輸出,然而這段程式碼告訴了我們,設計者透過函數綁定方式來動態替換輸出的格式,那麼我們先了解函數綁定是甚麼
## 函數綁定
```cpp!
std::bind(函式位址 ,placeholders::_1, placeholders::_2, ..., placeholders::_N);
```
`std::bind` 可以將函數與部分參數綁定,生成一個新函數,方便後續呼叫,有點像在麥當勞點套餐,你買套餐,配餐部分就會有 A 、 B 、 C 等等給你選,所以 `std::bind` 比較像固定某個菜色,後面配菜給你選並形成一個套餐,以下是一個簡單例子
```cpp
int add_n(int x, int n) {
return x + n;
};
int main() {
int x = 1;
int y = add_n(1, 2);
std::cout << "y : " << y << "\n";
return 0;
}
```
那如果今天我想要讓某個函數加上 2 呢 ? 我們可以透過 `std::bind` 來創建一個 `std::function` 的泛型函數容器,但多半都用 `auto` 來表達,所以呢我們將 2 先行帶入,並透過 `std::bind` 來將參數與函數綁定後,形成一個新的函數給我們使用
* `placeholders` : 為形成新的函數後要使用的參數,並依照順序填入,它告訴 `std::bind` 這個位置的參數在呼叫新函數時再傳入
* `_1` : 對應的是新函數呼叫時的第一個參數
* `_2` : 對應第二個參數
```cpp
using namespace std::placeholders;
int add_n(int x, int n) {
return x + n;
};
int main() {
int x = 1;
auto add_2 = std::bind(add_n, _1, 2);
// std::function<int(int)> add_2 = std::bind(add_n, _1, 2);
int y = add_2(x);
std::cout << "y : " << y << "\n";
return 0;
}
```
## 重新回顧
```cpp!
std::unique_ptr<Output> output = std::unique_ptr<Output>(Output::Create(options));
app.SetEncodeOutputReadyCallback(std::bind(&Output::OutputReady, output.get(), _1, _2, _3, _4));
```
再回來看這段程式碼,我們就會知道其要被綁定函數為 `&Output::OutputReady`,但這邊要注意它是一個**成員函數指標** (pointer-to-member-function),型態為 `Return_Type (Output::*)(Args...)`,不像一般函數指標,它需要知道「要在哪個物件上呼叫」,也就是他需要知道物件指標,所以在綁定時需要額外提供一個對應的物件指標給他,所以透過 `output.get()` 取得智慧指標所擁有的原生指標給他使用
接著我們看到 `SetEncodeOutputReadyCallback` 函數
```cpp!
typedef std::function<void(void *, size_t, int64_t, bool)> EncodeOutputReadyCallback;
void SetEncodeOutputReadyCallback(EncodeOutputReadyCallback callback) { encode_output_ready_callback_ = callback; }
```
程式碼往裡面點,越看越霧煞煞,當然我們已經知道他要綁定一個函數,那這個函數是甚麼呢 ? , 這邊看到 `std::function`,而 `std::function` 是一個容器,這個容器裡面可以將泛型函數放入,所以被稱做**泛型函式封裝器**,接著 `<void(void *, size_t, int64_t, bool)>` 是表示這個函數回傳與參數,所以這個函數回傳是 `void` ,參數為 `void*`、 `size_t` .... 這樣的函數,如果我今天要將類別成員方法進行綁定,就會需要先綁定物件,所以要透過 `std::bind` 來達成
:::info
在 C++ 中,`std::function` 可以接收
* 普通函數指標
* 成員函數 : 但必須先綁物件,所以用 `std::bind` 或 lambda
* lambda : 有對應的 operator()
* functor : 類別重載 operator()
:::
## 總結
其實這樣函數寫法很容易讓人搞混,當初也是看不太懂,現在才開始慢慢了解,由於 Callback 函數往往要使用不同功能的函數,上面這個 Case 就是要接住一個**類別成員函數**才會造成閱讀上的困難,不然其實用函數指標就解決了