lambda 和 std::function
1. Lambda函數(shù)的用處
lambda 表達(dá)式可以方便地構(gòu)造匿名函數(shù),如果你的代碼里面存在大量的小函數(shù),而這些函數(shù)一般只被調(diào)用一次,那么不妨將他們重構(gòu)成 lambda 表達(dá)式。
C++11 的 lambda 表達(dá)式規(guī)范如下:
| [ capture ] ( params ) mutable exception attribute -> ret { body } | (1) |
| [ capture ] ( params ) -> ret { body } | (2) |
| [ capture ] ( params ) { body } | (3) |
| [ capture ] { body } | (4) |
其中
-
(1) 是完整的 lambda 表達(dá)式形式,
-
(2) const 類型的 lambda 表達(dá)式,該類型的表達(dá)式不能修改捕獲("capture")列表中的值。
-
(3)省略了返回值類型的 lambda 表達(dá)式,但是該 lambda 表達(dá)式的返回類型可以按照下列規(guī)則推演出來:
-
如果 lambda 代碼塊中包含了 return 語句,則該 lambda 表達(dá)式的返回類型由 return 語句的返回類型確定。
-
如果沒有 return 語句,則類似 void f(...) 函數(shù)。
-
-
省略了參數(shù)列表,類似于無參函數(shù) f()。
mutable 修飾符說明 lambda 表達(dá)式體內(nèi)的代碼可以修改被捕獲的變量,并且可以訪問被捕獲對象的 non-const 方法。
exception 說明 lambda 表達(dá)式是否拋出異常(noexcept),以及拋出何種異常,類似于void f() throw(X, Y)。
attribute 用來聲明屬性。
另外,capture 指定了在可見域范圍內(nèi) lambda 表達(dá)式的代碼內(nèi)可見得外部變量的列表(詳見第三節(jié)),具體解釋如下:
-
[a,&b] a變量以值的方式被捕獲,b以引用的方式被捕獲。
-
[this] 以值的方式捕獲 this 指針。
-
[&] 以引用的方式捕獲所有的外部自動變量。
-
[=] 以值的方式捕獲所有的外部自動變量。
-
[] 不捕獲外部的任何變量。
此外,params 指定 lambda 表達(dá)式的參數(shù)。
2. Lambda函數(shù)中的變量截取
捕獲:
(1)值捕獲
與參數(shù)傳值異同,
相同點(diǎn):值捕獲的前提是變量可以拷貝
不同點(diǎn):被捕獲的變量在lambda被創(chuàng)建時(shí)拷貝,而非調(diào)用時(shí)才拷貝
void f(const int*);void g(){ const int N = 10; [=]{ int arr[N]; // not an odr-use: refers to g's const int N f(&N); // odr-use: causes N to be captured (by copy) // &N is the address of the closure object's member N, not g's N }();}(2)引用捕獲
與引用傳參類似,引用捕獲保存的是引用,值會發(fā)生變化。
#include <iostream> auto make_function(int& x) { return [&]{ std::cout << x << '\n'; };//return [=]{ std::cout << x << '\n'; }; //值捕獲打印3,創(chuàng)建時(shí)傳入的值是多少就是多少 } int main() { int i = 3; auto f = make_function(i); // the use of x in f binds directly to i i = 5; f(); // OK; prints 5}(3)隱式捕獲
[] 空捕獲列表
[name1, name2, ...] 捕獲一系列變量
[&] 引用捕獲, 讓編譯器自行推導(dǎo)捕獲列表
[=] 值捕獲, 讓編譯器執(zhí)行推導(dǎo)應(yīng)用列表
void f3() { float x, &r = x; [=] { // x and r are not captured (appearance in a decltype operand is not an odr-use) decltype(x) y1; // y1 has type float decltype((x)) y2 = y1; // y2 has type float const& because this lambda // is not mutable and x is an lvalue decltype(r) r1 = y1; // r1 has type float& (transformation not considered) decltype((r)) r2 = y2; // r2 has type float const& };}當(dāng)然C++14,C++17,C++20對lambda做了更多的更新,這里就不多講了。
4. Lambda函數(shù)和STL?
lambda函數(shù)的引入為STL的使用提供了極大的方便。比如下面這個(gè)例子,當(dāng)你想便利一個(gè)vector的時(shí)候,原來你得這么寫:
vector<int> v; v.push_back( 1 ); v.push_back( 2 ); //... for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ ) { cout << *itr; }現(xiàn)在有了lambda函數(shù)你就可以這么寫:
vector<int> v; v.push_back( 1 ); v.push_back( 2 ); //... for_each( v.begin(), v.end(), [] (int val) { cout << val; } );而且這么寫了之后執(zhí)行效率反而提高了。因?yàn)榫幾g器有可能使用”循環(huán)展開“來加速執(zhí)行過程。
?
仿函數(shù)(functor)
什么是仿函數(shù)(functor)
????functor的英文解釋為something that performs a function,即其行為類似函數(shù)的東西。C++中的仿函數(shù)是通過在類中重載()運(yùn)算符實(shí)現(xiàn),使你可以像使用函數(shù)一樣來創(chuàng)建類的對象。
仿函數(shù)(functor)的實(shí)現(xiàn)及使用
//?this?is?a?functor struct?add_x?{add_x(int?x)?:?x(x)?{}int?operator()(int?y)?{?return?x?+?y;?} private:int?x; }; //?usage: add_x?add42(42);?//?create?an?instance?of?the?functor?class int?i?=?add42(8);?//?and?"call"?it assert(i?==?50);?//?and?it?added?42?to?its?argument std::vector<int>?in;?//?assume?this?contains?a?bunch?of?values) std::vector<int>?out; //?Pass?a?functor?to?std::transform,?which?calls?the?functor?on?every?element? //?in?the?input?sequence,?and?stores?the?result?to?the?output?sequence std::transform(in.begin(),?in.end(),?out.begin(),?add_x(1));? assert(out[i]?==?in[i]?+?1);?//?for?all?i為什么使用仿函數(shù)(functor)
-
迭代和計(jì)算邏輯分離
????使用仿函數(shù)可以使迭代和計(jì)算分離開來。因而你的functor可以應(yīng)用于不同場合,在STL的算法中就大量使用了functor,下面是STL中for_each中使用functor的示例:
struct?sum {sum(int?*?t):total(t){};int?*?total;void?operator()(int?element){*total+=element;} }; int?main() {int?total?=?0;sum?s(&total);int?arr[]?=?{0,?1,?2,?3,?4,?5};std::for_each(arr,?arr+6,?s);cout?<<?total?<<?endl;?//?prints?total?=?15; }-
參數(shù)可設(shè)置
????可以很容易通過給仿函數(shù)(functor)設(shè)置參數(shù),來實(shí)現(xiàn)原本函數(shù)指針才能實(shí)現(xiàn)的功能,看下面代碼:
class?CalculateAverageOfPowers { public:CalculateAverageOfPowers(float?p)?:?acc(0),?n(0),?p(p)?{}void?operator()?(float?x)?{?acc?+=?pow(x,?p);?n++;?}float?getAverage()?const?{?return?acc?/?n;?} private:float?acc;int???n;float?p; };????這個(gè)仿函數(shù)的功能是求給定值平方或立方運(yùn)算的平均值。只需要這樣來聲明一個(gè)對象即可:
CalculateAverageOfPowers?my_cal(2);-
有狀態(tài)
????與普通函數(shù)另一個(gè)區(qū)別是仿函數(shù)(functor)是有狀態(tài)的,所以可以進(jìn)行諸如下面這種操作:
CalculateAverage?avg; avg?=?std::for_each(dataA.begin(),?dataA.end(),?avg); avg?=?std::for_each(dataB.begin(),?dataB.end(),?avg); avg?=?std::for_each(dataC.begin(),?dataC.end(),?avg);????對多個(gè)不同的數(shù)據(jù)集進(jìn)行取平均。
-
性能
????我們看一下2中寫的代碼:
std::transform(in.begin(),?in.end(),?out.begin(),?add_x(1));????編譯器可以準(zhǔn)確知道std::transform需要調(diào)用哪個(gè)函數(shù)(add_x::operator)。這意味著它可以內(nèi)聯(lián)這個(gè)函數(shù)調(diào)用。而如果使用函數(shù)指針,編譯器不能直接確定指針指向的函數(shù),而這必須在程序運(yùn)行時(shí)才能得到并調(diào)用。
????一個(gè)例子就是比較std::sort 和qsort ,STL的版本一般要快5-10倍。
-
總結(jié)
????當(dāng)然,前3點(diǎn)都可以使用傳統(tǒng)的函數(shù)和指針實(shí)現(xiàn),但是用仿函數(shù)(functor)可以讓這種實(shí)現(xiàn)變的更加簡單。
?
函數(shù)對象包裝器之std::function
std::function? ?是C++11中又一個(gè)新的概念。想想在之前的C++中,function一直是一個(gè)尷尬的存在,它作為程序的一部分,被“釘死”在程序的代碼段,而且可調(diào)用的對象五花八門。
C++11打破了這個(gè)尷尬,統(tǒng)一可調(diào)用對象的概念,可調(diào)用對象有以下幾種定義:
-
是一個(gè)函數(shù)指針,參考 C++ 函數(shù)指針和函數(shù)類型;
-
是一個(gè)具有operator()成員函數(shù)的類的對象;
-
可被轉(zhuǎn)換成函數(shù)指針的類對象;
-
一個(gè)類成員函數(shù)指針;
C++中可調(diào)用對象的雖然都有一個(gè)比較統(tǒng)一的操作形式,但是定義方法五花八門,這樣就導(dǎo)致使用統(tǒng)一的方式保存可調(diào)用對象或者傳遞可調(diào)用對象時(shí),會十分繁瑣。C++11中提供了std::function和std::bind統(tǒng)一了可調(diào)用對象的各種操作。
舉個(gè)例子:
// 普通函數(shù)int add(int a, int b){return a+b;} // lambda表達(dá)式auto mod = [](int a, int b){ return a % b;} // 函數(shù)對象類struct divide{ int operator()(int denominator, int divisor){ return denominator/divisor; }};上述三種可調(diào)用對象雖然類型不同,但是共享了一種調(diào)用形式:
int(int ,int)std::function就可以將上述類型保存起來,如下:
std::function<int(int ,int)> a = add; std::function<int(int ,int)> b = mod ; std::function<int(int ,int)> c = divide();總結(jié)一下:
std::function是一個(gè)類模版,是一種通用、多態(tài)的函數(shù)封裝。
std::function對象可以對普通函數(shù)、Lambda表達(dá)式、函數(shù)指針、以及其它函數(shù)對象等 進(jìn)行存儲、復(fù)制、和調(diào)用操作。
std::function對象是對可調(diào)用實(shí)體類型安全的包裹(我們知道像函數(shù)指針這類可調(diào)用實(shí)體,是類型不安全的)。
std::function通常是一個(gè)函數(shù)對象類,它包裝其它任意的函數(shù)對象,被包裝的函數(shù)對象具有類型為T1, …,TN的N個(gè)參數(shù),并且返回一個(gè)可轉(zhuǎn)換到R類型的值。
std::function使用 模板轉(zhuǎn)換構(gòu)造函數(shù)接收被包裝的函數(shù)對象;特別是,閉包類型可以隱式地轉(zhuǎn)換為std::function。
最簡單的理解就是:
通過std::function對可調(diào)用實(shí)體(普通函數(shù)、Lambda表達(dá)式、函數(shù)指針、以及其它函數(shù)對象等)的封裝,形成一個(gè)新的可調(diào)用的std::function對象;讓我們不再糾結(jié)那么多的可調(diào)用實(shí)體
下邊貼段代碼加深一下理解:
#include <functional>#include <iostream>using namespace std; std::function< int(int) > Functional; // 普通函數(shù)int TestFunc(int a){ return a;} // Lambda表達(dá)式auto lambda = [](int a)->int{ return a; }; // 仿函數(shù)(functor)class Functor{public: int operator()(int a){ return a; }}; // 1.類成員函數(shù)// 2.類靜態(tài)函數(shù)class TestClass{public: int ClassMember(int a) { return a; } static int StaticMember(int a) { return a; }}; int main(){ // 普通函數(shù) Functional = TestFunc; int result = Functional(10); cout << "普通函數(shù):"<< result << endl; // Lambda表達(dá)式 Functional = lambda; result = Functional(20); cout << "Lambda表達(dá)式:"<< result << endl; // 仿函數(shù) Functor testFunctor; Functional = testFunctor; result = Functional(30); cout << "仿函數(shù):"<< result << endl; // 類成員函數(shù) TestClass testObj; Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1); result = Functional(40); cout << "類成員函數(shù):"<< result << endl; // 類靜態(tài)函數(shù) Functional = TestClass::StaticMember; result = Functional(50); cout << "類靜態(tài)函數(shù):"<< result << endl; return 0;}總結(jié)
以上是生活随笔為你收集整理的lambda 和 std::function的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动语义、完美转发
- 下一篇: std::mutex