天天看點

參數綁定

對于那種隻有一兩個地方使用的簡單操作,lambda表達式是最有用的。如果我們需要在很多地方使用相同的操作,通常應該定義一個函數,而不是多次編寫相同的lambda表達式。類似的,如果一個操作需要很多語句才能完成,通常使用函數更好。

如果lambda的捕獲清單為空,通常可以用函數來代替它。如前章節所示,既可以用一個lambda,也可以用函數isShorter來實作将vector中的單詞按長度排序。類似的,對于列印vector内容的lambda,編寫一個函數來替換它也是十分容易的事情,這個函數隻需要接受一個string并在标準輸出上列印它即可。

但是,對于捕獲局部變量的lambda,用函數來替換它就不是那麼容易了。例如,我們用在find_if調用中的lambda比較一個string和一個給定大小。我們可以很容易地編寫一個完成同樣工作的函數:

bool check_size(const string &s,string::size_type sz)

{

  return s.size()>=sz;

}

但是,我們不能用這個函數作為find_if的一個參數。如前文所示,find_if接受一個一進制謂詞,是以傳遞給find_if的可調用對象必須接受單一參數。binggies傳遞給find_if的lambda使用捕獲清單來儲存sz。為了用check_size來代替此lambda,必須解決如何向sz形參傳遞一個參數的問題。

标準庫bind函數

我們可以解決向check_size傳遞一個長度參數的問題,方法是使用一個新的名為bind的标準庫函數,它定義在頭檔案functional中。可以将bind函數看作一個通用的函數擴充卡,它接受一個可調用對象,生成一個新的可調用對象來“适應”原對象的參數清單。

調用bind的一般形式:

auto newCallable=bind(callable,arg_list);

其中,newCallable本身是一個可調用對象,arg_list是一個逗号分隔的參數清單,對應給定的callable的參數。即,當我們調用newCallable時,newCallable會調用callable,并傳遞給它arg_list中的參數。

arg_list中的參數可能包含形如_n的名字,其中n是一個整數。這些參數是“占位符”,表示newCallable的參數,它們占據了傳遞給newCallable的參數的“位置”。數值n表示生成的可調用對象中參數的位置:_1為newCallable的第一個參數,_2為第二個參數,以此類推。

綁定check_size的sz參數

作為一個簡單的例子,我們将使用bind生成一個調用check_size的對象,如下所示,它用一個定值作為其大小參數來調用check_size:

//check6是一個可調用對象,接受一個string類型的參數

//并用此string和值6來調用check_size

auto check6=bind(check_size,_1,6);

此bind調用隻有一個占位符,表示check6隻接受單一參數。占位符出現在arg_list的第一個位置,表示check6的此參賽對應check_size的第一個參數。此參數是一個const string&。是以,調用check6必須傳遞給它一個string類型的參數,check6會将此參數傳遞給check_size。

string s="hello";

bool b1=check6(s); //check6(s)會調用check_size(s,6)

使用bind,我們可以将原來基于lambda的find_if調用:

auto wc=find_if(words.begin(),words.end(),[sz](const string &s)

替換為如下使用check_size的版本:

auto wc=find_if(words.begin(),words.end(),bind(check_size,_1,sz));

此bind調用生成一個可調用對象,将check_size的第二個參數綁定到sz的值。當find_if對words中的string調用這個對象時,這些對象會調用check_size,将給定的string和sz傳遞給它。是以,find_if可以有效地對輸入序列中每個string調用check_size,實作string的大小與sz的比較。

使用placeholders名字

名字_n都定義在一個名為placeholders的命名空間中,而這個命名空間本身定義在std命名空間中。為了使用這些名字,兩個命名空間都要寫上。例如,_1對應的using聲明為:

using std::placeholders::_1;

此聲明說明我們要使用的名字_1定義在命名空間placeholders中,而此命名空間又定義在命名空間std中。

對每個占位符名字,我們都必須提供一個單獨的using聲明。編寫這樣的聲明很煩人,也很容易出錯。可以使用另外一種不同形式的using語句,而不是分别聲明每個占位符,如下所示:

using namespace namespace_name;

這種形式說明希望所有來自namespace_name的名字都可以在我們的程式中直接使用。例如:

using namespace std::placeholders;

使得由placeholders定義的所有名字都可用。與bind函數一樣,placeholders命名空間也定義在functional頭檔案中。

bind的參數

如前文所述,我們可以用bind修正參數的值。更一般的,可以用bind綁定給定可調用對象中的參數或重新安排其順序。例如,假定f是一個可調用對象,它有5個參數,則下面對bind的調用:

//g是一個有兩個參數的可調用對象

auto g=bind(f,a,b,_2,c,_1);

生成一個新的可調用對象,它有兩個參數,分别用占用符_2和_1表示。這個新的可調用對象将它自己的參數作為第三個和第五個參數傳遞給f。f的第一個、第二個和第四個參數分别被綁定到給定的值a、b和c上。

傳遞給g的參數按位置綁定到占位符。即,第一個參數綁定到_1,第二個參數綁定到_2.是以,當我們調用g時,其第一個參數将被傳遞給f作為最後一個參數,第二個參數将被傳遞給f作為第三個參數。實際上,這個bind調用會将

g(_1,_2);

映射為

f(a,b,_2,c,_1)

即,對g的調用會調用f,用g的參數代替占位符,再加上綁定的參數a、b和c。例如,調用g(X,Y)會調用

f(a,b,Y,c,X);

用bind重排參數順序

下面是bind重排參數順序的一個具體例子,我們可以用bind颠倒isShorter的含義:

//按單詞長度由短至長排序

sort(words.begin(),words.end(),isShorter);

//按單詞長度由長至短排序

sort(words.begin(),words.end(),bind(isShorter,_2,_1));

在第一個調用中,當sort需要比較兩個元素A和B時,它會調用isShorter(A,B).在第二個對sort的調用中,傳遞給isShorter的參數被交換過來了。是以,當sort比較兩個元素時,就好像調用isShorter(B,A)一樣。

綁定引用參數

預設情況下,bind的哪些不是占位符的參數被拷貝到bind傳回的可調用對象中。但是,與lambda類似,有時對有些綁定的參數我們希望以引用方式傳遞,或是要綁定參數的類型無法拷貝。

 例如,為了替換一個引用方式捕獲ostream的lambda:

//os是一個局部變量,引用一個輸出流 c是一個局部變量,類型為char

for_each(words.begin,words.end(),[&os,c] (const string &s) { os<<s<<c;});

可以很容易地編寫一個函數,完成相同的工作:

ostream & print(ostream &os,const string &s,char c)

  os<<s<<c;

但是,不能直接用bind來代替對os的捕獲:

//錯誤:不能拷貝os

for_each(words.begin(),words.end(),bind(print,os,_1,' '));

原因在于bind拷貝其參數,而我們不能拷貝一個ostream,如果我們希望傳遞給bind一個對象而又不是拷貝它,就必須使用标準庫ref函數:

for_each(words.begin(),words.end(),bind(print,ref(os),_1,' '));

函數ref傳回一個對象,包含給定的引用,此對象是可以拷貝的。标準庫中還有一個cref函數,生成一個儲存const引用的類。與bind一樣,函數ref和cref也定義在頭檔案functional中。