對(duì)于那種只在一兩個(gè)地方使用的簡(jiǎn)單操作, lambda 表達(dá)式是最有用的。如果我們需要在很多地方使用相同的操作,通常應(yīng)該定義一個(gè)函數(shù),而不是多次編寫(xiě)相同的 lambda 表達(dá)式。類(lèi)似的,如果一個(gè)操作需要很多語(yǔ)句才能完成,通常使用函數(shù)更好。
如果 lambda 的捕獲列表為空,通??梢杂煤瘮?shù)來(lái)代替它。例如我們既可以用一個(gè) lambda,也可以用函數(shù)來(lái)實(shí)現(xiàn)將 vector 中的單詞按長(zhǎng)度排序。類(lèi)似的,對(duì)于打印 vector 內(nèi)容的 lambda,編寫(xiě)一個(gè)函數(shù)來(lái)替換它也是很容易的事情,這個(gè)函數(shù)只需接受一個(gè) string 并在標(biāo)準(zhǔn)輸出上打印它即可。
但是,對(duì)于捕獲局部變量的 lambda,用函數(shù)來(lái)替換它就不是那么容易了。例如,我們用在 find_if 調(diào)用中的 lambda 比較一個(gè) string 和一個(gè)給定大小。我們可以很容易地編寫(xiě)一個(gè)完成同樣工作的函數(shù):
bool check_size(const string &s, string::size_type sz) {
return s.size() >= sz;
}
但是,我們不能用這個(gè)函數(shù)作為 find_if 的一個(gè)參數(shù)。如前文所示,find_if 接受一個(gè)一元謂詞,因此傳遞給 find_if 的可調(diào)用對(duì)象必須接受單一參數(shù)。biggies 傳遞給 find_if 的 lambda 使用捕獲列表來(lái)保存sz。為了用 check_size 來(lái)代替此 lambda,必須解決如何向 sz 形參傳遞一個(gè)參數(shù)的問(wèn)題。
標(biāo)準(zhǔn)庫(kù) bind 函數(shù)
我們可以解決向 check_size 傳遞一個(gè)長(zhǎng)度參數(shù)的問(wèn)題,方法是使用一個(gè)新的名為 bind 的標(biāo)準(zhǔn)庫(kù)函數(shù),它定義在頭文件 functional 中??梢詫?bind 函數(shù)看作一個(gè)通用的函數(shù)適配器,它接受一個(gè)可調(diào)用對(duì)象,生成一個(gè)新的可調(diào)用對(duì)象來(lái)“適應(yīng)”原對(duì)象的參數(shù)列表。
調(diào)用bind的一般形式為:
auto newCallable = bind(callable, args_list);
其中,newCallable 本身是一個(gè)可調(diào)用對(duì)象,args_list 是一個(gè)逗號(hào)分隔的參數(shù)列表,對(duì)應(yīng)給定的 callable 的參數(shù)。即,當(dāng)我們調(diào)用 newCallable 時(shí),newCallable 會(huì)調(diào)用 callable,并傳遞給它 args_list 中的參數(shù)。
args_list 中的參數(shù)可能包含形如 _n 的名字,其中 n 是一個(gè)整數(shù)。這些參數(shù)是“占位符”,表示 newCallable 的參數(shù),它們占據(jù)了傳遞給 newCallable 的參數(shù)的“位置”。數(shù)值 n 表示生成的可調(diào)用對(duì)象中參數(shù)的位置:_1 為 newCallable 的第一個(gè)參數(shù),_2 為第二個(gè)參數(shù),依此類(lèi)推。
綁定 check_size 的 sz 參數(shù)
作為一個(gè)簡(jiǎn)單的例子,我們將使用 bind 生成一個(gè)調(diào)用 check_size 的對(duì)象,如下所示,它用一個(gè)定值作為其大小參數(shù)來(lái)調(diào)用 check_size:
// check6 是一個(gè)可調(diào)用對(duì)象,接受一個(gè) string 類(lèi)型的參數(shù)
// 并用此 string 和值 6 來(lái)調(diào)用 check
auto check6 = bind(check_size, _1, 6);
此 bind 調(diào)用只有一個(gè)占位符,表示 check6 只接受單一參數(shù)。占位符出現(xiàn)在 args_list 的第一個(gè)位置,表示 check6 的此參數(shù)對(duì)應(yīng) check_size 的第一個(gè)參數(shù)。此參數(shù)是一個(gè) const string&。因此,調(diào)用 check6 必須傳遞給它一個(gè) string 類(lèi)型的參數(shù),check6會(huì)將此參數(shù)傳遞給 check_size。
string s = "hello";
bool b1 = check6(s); // check6(s) 會(huì)調(diào)用 check_size(s, 6)
使用 bind,我們可以將原來(lái)基于 lambda 的 find_if 調(diào)用:
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
替換為如下使用 check_size 的版本:
auto wc = find_if(words.begin(), words.end(),
bind(check_size, _1, sz));
此 bind 調(diào)用生成一個(gè)可調(diào)用對(duì)象,將 check_size 的第二個(gè)參數(shù)綁定到 sz 的值。當(dāng) find_if 對(duì) words 中的 string 調(diào)用這個(gè)對(duì)象時(shí),這些對(duì)象會(huì)調(diào)用 check_size,將給定的 string 和 sz 傳遞給它。因此,find_if 可以有效地對(duì)輸入序列中每個(gè) string 調(diào)用 check_size,實(shí)現(xiàn) string 的大小與 sz 的比較。
使用 placeholders 名字
名字 _n 都定義在一個(gè)名為 placeholders 的命名空間中,而這個(gè)命名空間本身定義在 std 命名空間中。為了使用這些名字,兩個(gè)命名空間都要寫(xiě)上。與我們的其他例子類(lèi)似,對(duì) bind 的調(diào)用代碼假定之前己經(jīng)恰當(dāng)?shù)厥褂昧?using 聲明。例如,_1 對(duì)應(yīng)的 using 聲明為:
using std::placeholders::_1;
此聲明說(shuō)明我們要使用的名字 _1 定義在命名空間 placeholders 中,而此命名空間又定義在命名空間 std 中。
對(duì)每個(gè)占位符名字,我們都必須提供一個(gè)單獨(dú)的 using 聲明。編寫(xiě)這樣的聲明很煩人,也很容易出錯(cuò)。可以使用另外一種不同形式的 using 語(yǔ)句,而不是分別聲明每個(gè)占位符,如下所示:
using namespace namespace_name;
這種形式說(shuō)明希望所有來(lái)自 namespace_name 的名字都可以在我們的程序中直接使用。例如:
using namespace std::placeholders;
使得由 placeholders 定義的所有名字都可用。與 bind 函數(shù)一樣,placeholders 命名空間也定義在 functional 頭文件中。
bind 的參數(shù)
如前文所述,我們可以用 bind 修正參數(shù)的值。更一般的,可以用 bind 綁定給定可調(diào)用對(duì)象中的參數(shù)或重新安排其順序。例如,假定 f 是一個(gè)可調(diào)用對(duì)象,它有 5 個(gè)參數(shù)則下面對(duì)bind的調(diào)用:
// g 是一個(gè)有兩個(gè)參數(shù)的可調(diào)用對(duì)象
auto g = bind(f, a, b, _2, c, _1);
生成一個(gè)新的可調(diào)用對(duì)象,它有兩個(gè)參數(shù),分別用占位符 _2 和 _1 表示。這個(gè)新的可調(diào)用對(duì)象將它自己的參數(shù)作為第三個(gè)和第五個(gè)參數(shù)傳遞給 f。f 的第一個(gè)、第二個(gè)和第四個(gè)參數(shù)分別被綁定到給定的值 a、b 和 c 上。
傳遞給 g 的參數(shù)按位置綁定到占位符。即,第一個(gè)參數(shù)綁定到 _1,第二個(gè)參數(shù)綁定到 _2。因此,當(dāng)我們調(diào)用 g 時(shí),其第一個(gè)參數(shù)將被傳遞給f作為最后一個(gè)參數(shù),第二個(gè)參數(shù)將被傳遞給f作為第三個(gè)參數(shù)。實(shí)際上,這個(gè) bind 調(diào)用會(huì)將
g(_1, _2)
映射為
f(a, b, _2, c, _1);
即,對(duì) g 的調(diào)用會(huì)調(diào)用 f,用 g 的參數(shù)代替占位符,再加上綁定的參數(shù) a、b 和 c。例如調(diào)用 g(X, Y) 會(huì)調(diào)用 f(a, b, Y, c, X)
用 bind 重排參數(shù)順序
下面是用 bind 重排參數(shù)順序的一個(gè)具體例子,我們可以用 bind 顛倒 isShroter 的含義:
// 按單詞長(zhǎng)度由短至長(zhǎng)排序
sort(words.begin(), words.end(), isShorter);
// 按單詞長(zhǎng)度由長(zhǎng)至短排序
sort(words.begin(), words.end(), bind(isShorter, _2, _1));
在第一個(gè)調(diào)用中,當(dāng) sort 需要比較兩個(gè)元素 A 和 B 時(shí),它會(huì)調(diào)用 isShorter(A, B)。在第二個(gè)對(duì) sort 的調(diào)用中,傳遞給 isShorter 的參數(shù)被交換過(guò)來(lái)了。因此,當(dāng) sort 比較兩個(gè)元素時(shí),就好像調(diào)用 isShorter(B, A) 一樣。
綁定引用參數(shù)
默認(rèn)情況下,bind 的那些不是占位符的參數(shù)被拷貝到 bind 返回的可調(diào)用對(duì)象中。但是,與 lambda 類(lèi)似,有時(shí)對(duì)有些綁定的參數(shù)我們希望以引用方式傳遞,或是要綁定參數(shù)的類(lèi)型無(wú)法拷貝。
例如,為了替換一個(gè)引用方式捕獲 ostream 的 lambda:
// os 是一個(gè)局部變量,引用一個(gè)輸出流
// c 是一個(gè)局部變量,類(lèi)型為 char
for_each(words.begin(), words.end(), [&os, c](const string &s) { os << s << c; });
可以很容易地編寫(xiě)一個(gè)函數(shù),完成相同的工作:
ostream &print(ostream &os, const string &s, char c) {
return os << s << c;
}
但是,不能直接用 bind 來(lái)代替對(duì) os 的捕獲
// 錯(cuò)誤:不能拷貝os
for_each(words.begin(), words.end(), bind(print, os, _1, ' '));
原因在于 bind 拷貝其參數(shù),而我們不能拷貝一個(gè) ostream。如果我們希望傳遞給 bind 一個(gè)對(duì)象而又不拷貝它,就必須使用標(biāo)準(zhǔn)庫(kù) 函數(shù):
for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));
函數(shù) ref 返回一個(gè)對(duì)象,包含給定的引用,此對(duì)象是可以拷貝的。標(biāo)準(zhǔn)庫(kù)中還有一個(gè) 函數(shù),生成一個(gè)保存 const 引用的類(lèi)。與 bind 一樣,函數(shù) ref 和 cref 也定義在頭文件 functional 中。
向后兼容:參數(shù)綁定
舊版本 C++ 提供的綁定函數(shù)參數(shù)的語(yǔ)言特性限制更多,也更復(fù)雜。標(biāo)準(zhǔn)庫(kù)定義了兩個(gè)分
別名為 bind1st 和 bind2nd 的函數(shù)。類(lèi)似 bind,這兩個(gè)函數(shù)接受一個(gè)函數(shù)作為參數(shù),
生成一個(gè)新的可調(diào)用對(duì)象,該對(duì)象調(diào)用給定函數(shù),并將綁定的參數(shù)傳遞給它。但是,這
些函數(shù)分別只能綁定第一個(gè)或第二個(gè)參數(shù)。由于這些函數(shù)局限太強(qiáng),在新標(biāo)準(zhǔn)中已被棄
用(deprecated)。所謂被棄用的特性就是在新版本中不再支持的特性。新的 C++ 程序應(yīng)
該使用 bind。