# 【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 就是要接住一個**類別成員函數**才會造成閱讀上的困難,不然其實用函數指標就解決了