1、 可調用對象
在C++中,有“可調用對象”這么個概念,那么什么是調用對象呢?有哪些情況?我們來看看:
- 函數指針;
- 具有operator()成員函數的類對象(仿函數);
- 可以被轉換為函數指針的類對象;
- 類成員(函數)指針。
我們來看代碼:
//函數指針 void func(void) { //... } struct Foo { void operator()(void) { //... } }; struct Bar { using fr_t = void(*)(void); static void func(void) { //... } operator fr_t(void) { return func; } }; struct A { int mia; void mem_func(void) { //... } }; int main(void) { //函數指針 void(* func_ptr)(void) = &func; func_ptr(); //仿函數 Foo foo; foo(); //被轉為指針的類對象 Bar bar; bar(); //類成員函數指針 void (A::*mem_func_ptr)(void) = &A::mem_func; //類成員指針 int A::*mem_obj_ptr = &A::mia; A aa; (aa.*mem_func_ptr)(); aa.*mem_obj_ptr = 123; return 0; }
上述的對象都是可調用對象,這些對象的類型統稱為“可調用類型”。這些可調用對象都具有統一的操作形式,除了類成員指針之外,都是通過括號的方式來進行調用,但是定義的方法比較多,在C++11中增加了std::function來進行函數對象的調用。
2、 std::function
std::function是一個可調用對象的包裝器,他是一個類模板,可以容納除了類成員(函數)指針之外的所用可調用對象,通過指定他的模板參數,可以以統一的方式處理函數、函數對象、函數指針,並允許保存或者延遲執行。
當我們給std::function填入合適的函數簽名(即一個函數類型,只需要包括返回值和參數列表)之后,它就變成了一個可以容納所有這一類調用方式的“函數包裝器”。
#include <iostream> #include <functional> void func(void) { std::cout << __FUNCTION__ << std::endl; } class Foo { public: static int foo_func(int a) { std::cout << __FUNCTION__ << "(" << a << ")->: "; return a; } }; class Bar { public: int operator()(int a) { std::cout << __FUNCTION__ << "(" << a << ")->: "; return a; } }; int main(void) { //綁定一個普通函數 std::function<void(void)> fr1 = func; fr1(); //綁定一個靜態成員函數 std::function<int(int)> fr2 = Foo::foo_func; std::cout << fr2(111) << std::endl; //綁定一個仿函數 Bar bar; fr2 = bar; std::cout << fr2(111) << std::endl; return 0; }
執行結果:
std::function還可以取代函數指針的作用,因為它可以保存函數延遲執行,所以也適合做回調函數。
#include <iostream> #include <functional> class A { std::function<void()> callback; public: A(const std::function<void()> &f) : callback(f){} void notify(void) { callback(); } }; class Foo { public: void operator()(void) { std::cout << __FUNCTION__ << std::endl; } }; int main(void) { Foo foo; A aa(foo); aa.notify(); return 0; }
std::function還可以作為函數入參,比普通函數指針更加靈活和便利。
#include <iostream> #include <functional> void call_when_event(int x, const std::function<void(int)>& f) { if(!(x & 1)) //x % 2 == 0 { f(x); } } void output(int x) { std::cout << x << " "; } int main(void) { for(int i = 0; i < 10; i++) { call_when_event(i, output); } std::cout << std::endl; return 0; }
3、 std::bind綁定器
3.1 std::bind綁定器
std::bind用來將可調用對象與起參數一起進行綁定,綁定的結果使用std::function進行保存,並在我們需要調用的時候調用。它主要有兩大作用:
- 將可調用對象和參數綁定成為一個仿函數;
- 將多元(參數個數為n,n-1)可調用對象轉換成一元或者(n-1)元可調用對象,即只綁定部分對象。
我們來看實際使用:
#include <iostream> #include <functional> void call_when_event(int x, const std::function<void(int)>& f) { if(!(x & 1)) //x % 2 == 0 { f(x); } } void output(int x) { std::cout << x << " "; } void output2(int x) { std::cout << x + 2 << " "; } int main(void) { { auto fr = std::bind(output, std::placeholders::_1); for(int i = 0; i < 10; i++) { call_when_event(i, fr); } std::cout << std::endl; } { auto fr = std::bind(output2, std::placeholders::_1); for(int i = 0; i < 10; i++) { call_when_event(i, fr); } std::cout << std::endl; } return 0; }
通過代碼我們可以知道std::bind在函數外部通過綁定不同的函數,控制執行結果。這里我們還使用了std::placeholders占位符來綁定函數參數。
3.2 std::placeholders
通過std::placeholders占位符綁定函數參數,使得std::bind的使用非常靈活。std::placeholders決定函數占用位置取用輸入參數的第幾個參數。
#include <iostream> #include <functional> void output(int x, int y) { std::cout << x << " " << y << std::endl; } int main(void) { std::bind(output, 1, 2)(); //輸出:1 2 std::bind(output, std::placeholders::_1, 2)(1); //輸出:1 2 std::bind(output, 2, std::placeholders::_1)(1); //輸出:2 1 //std::bind(output, 2, std::placeholders::_2)(1); //error,沒有第二個參數 std::bind(output, 2, std::placeholders::_2)(1,2); //輸出:2 2,第一個參數被拋棄 std::bind(output, std::placeholders::_1, std::placeholders::_2)(1,2); //輸出:1 2 std::bind(output, std::placeholders::_2, std::placeholders::_1)(1,2); //輸出:2 1 return 0; }
3.3 std::bind+std::function
我們先看一組例子:
#include <iostream> #include <functional> class A { public: int mi = 0; void output(int x, int y) { std::cout << x << " " << y << std::endl; } }; int main(void) { A a; std::function<void(int, int)> fr = std::bind(&A::output, &a, std::placeholders::_1, std::placeholders::_2); fr(1, 2); std::function<int &(void)> fr_i = std::bind(&A::mi, &a); fr_i() = 123; std::cout << a.mi << std::endl; return 0; }
fr的類型是std::function<void(int, int)>,我們通過std::bind將A的成員函數output的指針和a綁定,並轉換為一個仿函數存儲在fr中。
通過std::bind將A的成員mi的指針和a綁定,返回的結果放入類型為std::function<int &(void)>的fr_i中,可以在需要的時候修改這個成員的值。
3.4 改善標准函數
假如我們有一個這樣的需求,對某個集合里面的元素進行統計,假設元素類型為int,那么我們需要對類型做比較,必須有一個閥值,即大於或者小於這個數。這里我們可以通過標准庫的函數來實現。
#include <iostream> #include <functional> int main() { std::vector<int> coll; for (int i = 1; i <= 10; ++i) { coll.push_back(i); } // 查找元素值大於10的元素的個數 // 也就是使得10 < elem成立的元素個數 int res = count_if(coll.begin(), coll.end(), std::bind1st(less<int>(), 10)); cout << res << endl; // 查找元素值小於10的元素的個數 // 也就是使得elem < 10成立的元素個數 res = count_if(coll.begin(), coll.end(), std::bind2nd(less<int>(), 10)); cout << res << endl; bool b = less<int>(10, 20); // 返回true return 0; }
本質上是對一個二元函數less<int>的調用,但是要分別調用bind1st,bind2nd,用起來比較繁雜,現在我們有bind,可以用統一的方式去實現。並不用關心是bind1st還是bind2nd,用bind即可。
#include <iostream> #include <functional> int main() { using std::placeholders::_1; std::vector<int> coll; //查找元素值大於10的元素個數 int count = std::count_if(coll.begin(), coll.end(), std::bind(less<int>(), 10, _1)); //查找元素值小於10的元素個數 count = std::count_if(coll.begin(), coll.end(), std::bind(less<int>(), _1, 10)); return 0; }
3.5 組合使用
bind可以綁定多個函數,假設我們要對某個集合在大於5小於10的元素個數進行統計,我們該怎么封裝呢?
首先封裝一個判斷是否大於5的函數,使其輸入只有一個參數,直接和5比較,大於5返回true。
std::bind(std::greater<int>(), std::placeholders::_1, 5);
同樣,我們需要封裝一個判斷是否小於10的函數,使其輸入一個參數,小於10則返回true。
std::bind(std::less_equal<int>(), std::placeholders::_1, 10);
然后組合,即可調用:
using std::placeholders::_1; auto f = std::bind(std::logical_and<bool>(), std::bind(std::greater<int>(), std::placeholders::_1, 5), std::bind(std::less_equal<int>(), std::placeholders::_1, 10)); int count = std::count_if(coll.begin(), coll.end(), f);