概述
std::bind函數定義在頭文件functional中,是一個函數模板,它就像一個函數適配器,接受一個可調用對象(callable object),生成一個新的可調用對象來“適應”原對象的參數列表。一般而言,我們用它可以把一個原本接收N個參數的函數fn,通過綁定一些參數,返回一個接收M個(M可以大於N,但這么做沒什么意義)參數的新函數。同時,使用std::bind函數還可以實現參數順序調整等操作。
函數原型
std::bind函數有兩種函數原型,定義如下:
-
template< class F, class... Args >
-
/*unspecified*/ bind( F&& f, Args&&... args );
-
-
template< class R, class F, class... Args >
-
/*unspecified*/ bind( F&& f, Args&&... args );
std::bind返回一個基於f的函數對象,其參數被綁定到args上。
f的參數要么被綁定到值,要么被綁定到placeholders(占位符,如_1, _2, ..., _n)。
參數
f:一個可調用對象(可以是函數對象、函數指針、函數引用、成員函數指針、數據成員指針),它的參數將被綁定到args上。
args:綁定參數列表,參數會被值或占位符替換,其長度必須與f接收的參數個數一致。
調用形式
調用std::bind的一般形式為:
auto newCallable = std::bind(callable, arg_list);
其中,newCallable本身是一個可調用對象,arg_list是一個逗號分隔的參數列表,對應給定的callable的參數。即,當我們調用newCallable時,newCallable會調用callable,並傳遞給它arg_list中的參數。
返回類型
std::bind的返回類型是一個未指定類型T的函數對象,這個類型T滿足以下條件: std::is_bind_expression<T>::value == true
T包含成員:
1.對象成員
一個由std::forward<F>(f)構造而來的std::decay<F>::type類型的對象,一個對象的每一個參數類型都是由std::forward<Arg_i>(arg_i)構造而來的std::decay<Arg_i>::type。簡單來說,std::decay<F>::type對象保存了調用std::bind時傳遞過來的f參數,而若干個std::decay<Arg_i>::type則保存了傳遞過來的args參數(一個std::decay<Arg_i>::type保存一個args)。
2.構造函數
如果T的所有對象成員都是可拷貝的,則它自身也是可拷貝的;如果它的所有對象成員都是可移動構造的,則它自身也是可移動構造的。
3.成員類型result_type(從C++17開始result_type已經被棄用)
·如果F是函數指針或者成員函數指針,result_type就是F的返回值類型
·如果F是一個擁有(或者說定義了)result_type的類類型,那么T的result_type就是F::result_type,即使result_type已經在T中被定義過
4.成員函數operator()
這是最應該了解的,因為在實際使用過程中,我們調用std::bind得到的返回值就是用來作為函數調用的。
bind的返回值T,假設我們這樣調用:g(a1, a2, a3, … ai); 此時g內部保存的std::decay<F>::type類型的對象將被調用, 它將會按照如下的方式來為a1, a2, …, ai 綁定值。
·如果調用bind時指定的是reference_wrapper<T>類型的,比如在調用bind時使用了std::ref 或者 std::cref來包裝args,那么調用g內部的這個對象時,對應參數會以T&類型傳入std::decay<F>::type類型的對象.
·如果在創建g時,使用了嵌套的bind,即g = bind(fn, args…)的參數列表args中,存在某個arg:使得std::is_bind_expression<decltype(arg)>::value == true, 那么這個嵌套的bind表達式會被立即調用,其返回值會被傳給ret里的_MyFun作為參數(也就是說嵌套的bind返回值會被當做ret調用時的參數), 如果嵌套的bind里用到了占位符placeholder, 這些placeholder將會從ret的調用參數ret(a1, a2, … ai)中對應位置選擇.
·如果在創建g時,使用了占位符placeholders, 即 g = bind(fn, arg1, arg2, …, _1, _2, …), (對於_1, _2…, 有std::is_placeholder<T>::value != 0). 那么a1, a2, …, ai會以轉發的形式forward<ai>(ai)傳遞給_MyFun, a1對應_1, a2對應_2, 以此類推.
否則,ret內部保存的args,即上文提到的_Mybargs(bind調用時綁定的參數們)將被以左值的形式傳給_MyFun以完成調用,這些參數和g有相同cv限定屬性.
如果g(a1, a2, …, ai)中,有哪些ai沒有匹配任何的placeholders,比如在調用bind時,placeholder只有_1, 而g(a1, a2, a3), 那么a2, a3就是沒有匹配的,沒有被匹配的參數將被求值,但是會被丟棄。
如果g被指定為volatile(volatile or const volatile),結果是未定義的。
上述內容都可以在C++文檔中找到。
從實踐出發,看下面一段程序來理解std::bind如何使用:
-
-
-
-
void fn(int n1, int n2, int n3) {
-
std::cout << n1 << " " << n2 << " " << n3 << std::endl;
-
}
-
-
int fn2() {
-
std::cout << "fn2 has called.\n";
-
return -1;
-
}
-
-
int main()
-
{
-
using namespace std::placeholders;
-
auto bind_test1 = std::bind(fn, 1, 2, 3);
-
auto bind_test2 = std::bind(fn, _1, _2, _3);
-
auto bind_test3 = std::bind(fn, 0, _1, _2);
-
auto bind_test4 = std::bind(fn, _2, 0, _1);
-
-
bind_test1(); //輸出1 2 3
-
bind_test2( 3, 8, 24);//輸出3 8 24
-
bind_test2( 1, 2, 3, 4, 5);//輸出1 2 3,4和5會被丟棄
-
bind_test3( 10, 24);//輸出0 10 24
-
bind_test3( 10, fn2());//輸出0 10 -1
-
bind_test3( 10, 24, fn2());//輸出0 10 24,fn2會被調用,但其返回值會被丟棄
-
bind_test4( 10, 24);//輸出24 0 10
-
return 0;
-
}
bind過程分析及傳參控制
過程合法性分析
設f需要的參數個數為N, bind(f…)中,提供的值的個數為V, 提供的占位符個數為S。對於合法的bind調用,必有 N == V + S. 如果V + S 超出N或者小於N, 編譯都會報錯。
bind返回值的傳參調用
·參數個數
f的調用中提供的參數與占位符數量有關,從程序中可以看出。
·參數順序
參見程序運行結果,參數順序與std::placeholders中的順序一致,因此我們可以用bind來重排參數順序。
這些只是std::bind的基本用法,對std::bind的引入是C++11的一大亮點,將其與lambda表達式、智能指針、綁定引用參數等知識相結合會明顯改變原有的代碼編寫。std::bind的高級用法還需要更深入學習。