lambda 和 std::function
1. Lambda函數的用處
lambda 表達式可以方便地構造匿名函數,如果你的代碼里面存在大量的小函數,而這些函數一般只被調用一次,那么不妨將他們重構成 lambda 表達式。
C++11 的 lambda 表達式規范如下:
| [ capture ] ( params ) mutable exception attribute -> ret { body } | (1) |
| [ capture ] ( params ) -> ret { body } | (2) |
| [ capture ] ( params ) { body } | (3) |
| [ capture ] { body } | (4) |
其中
-
(1) 是完整的 lambda 表達式形式,
-
(2) const 類型的 lambda 表達式,該類型的表達式不能修改捕獲("capture")列表中的值。
-
(3)省略了返回值類型的 lambda 表達式,但是該 lambda 表達式的返回類型可以按照下列規則推演出來:
-
如果 lambda 代碼塊中包含了 return 語句,則該 lambda 表達式的返回類型由 return 語句的返回類型確定。
-
如果沒有 return 語句,則類似 void f(...) 函數。
-
-
省略了參數列表,類似于無參函數 f()。
mutable 修飾符說明 lambda 表達式體內的代碼可以修改被捕獲的變量,并且可以訪問被捕獲對象的 non-const 方法。
exception 說明 lambda 表達式是否拋出異常(noexcept),以及拋出何種異常,類似于void f() throw(X, Y)。
attribute 用來聲明屬性。
另外,capture 指定了在可見域范圍內 lambda 表達式的代碼內可見得外部變量的列表(詳見第三節),具體解釋如下:
-
[a,&b] a變量以值的方式被捕獲,b以引用的方式被捕獲。
-
[this] 以值的方式捕獲 this 指針。
-
[&] 以引用的方式捕獲所有的外部自動變量。
-
[=] 以值的方式捕獲所有的外部自動變量。
-
[] 不捕獲外部的任何變量。
此外,params 指定 lambda 表達式的參數。
2. Lambda函數中的變量截取
捕獲:
(1)值捕獲
與參數傳值異同,
相同點:值捕獲的前提是變量可以拷貝
不同點:被捕獲的變量在lambda被創建時拷貝,而非調用時才拷貝
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)引用捕獲
與引用傳參類似,引用捕獲保存的是引用,值會發生變化。
#include <iostream> auto make_function(int& x) { return [&]{ std::cout << x << '\n'; };//return [=]{ std::cout << x << '\n'; }; //值捕獲打印3,創建時傳入的值是多少就是多少 } 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, ...] 捕獲一系列變量
[&] 引用捕獲, 讓編譯器自行推導捕獲列表
[=] 值捕獲, 讓編譯器執行推導應用列表
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& };}當然C++14,C++17,C++20對lambda做了更多的更新,這里就不多講了。
4. Lambda函數和STL?
lambda函數的引入為STL的使用提供了極大的方便。比如下面這個例子,當你想便利一個vector的時候,原來你得這么寫:
vector<int> v; v.push_back( 1 ); v.push_back( 2 ); //... for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ ) { cout << *itr; }現在有了lambda函數你就可以這么寫:
vector<int> v; v.push_back( 1 ); v.push_back( 2 ); //... for_each( v.begin(), v.end(), [] (int val) { cout << val; } );而且這么寫了之后執行效率反而提高了。因為編譯器有可能使用”循環展開“來加速執行過程。
?
仿函數(functor)
什么是仿函數(functor)
????functor的英文解釋為something that performs a function,即其行為類似函數的東西。C++中的仿函數是通過在類中重載()運算符實現,使你可以像使用函數一樣來創建類的對象。
仿函數(functor)的實現及使用
//?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為什么使用仿函數(functor)
-
迭代和計算邏輯分離
????使用仿函數可以使迭代和計算分離開來。因而你的functor可以應用于不同場合,在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; }-
參數可設置
????可以很容易通過給仿函數(functor)設置參數,來實現原本函數指針才能實現的功能,看下面代碼:
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; };????這個仿函數的功能是求給定值平方或立方運算的平均值。只需要這樣來聲明一個對象即可:
CalculateAverageOfPowers?my_cal(2);-
有狀態
????與普通函數另一個區別是仿函數(functor)是有狀態的,所以可以進行諸如下面這種操作:
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);????對多個不同的數據集進行取平均。
-
性能
????我們看一下2中寫的代碼:
std::transform(in.begin(),?in.end(),?out.begin(),?add_x(1));????編譯器可以準確知道std::transform需要調用哪個函數(add_x::operator)。這意味著它可以內聯這個函數調用。而如果使用函數指針,編譯器不能直接確定指針指向的函數,而這必須在程序運行時才能得到并調用。
????一個例子就是比較std::sort 和qsort ,STL的版本一般要快5-10倍。
-
總結
????當然,前3點都可以使用傳統的函數和指針實現,但是用仿函數(functor)可以讓這種實現變的更加簡單。
?
函數對象包裝器之std::function
std::function? ?是C++11中又一個新的概念。想想在之前的C++中,function一直是一個尷尬的存在,它作為程序的一部分,被“釘死”在程序的代碼段,而且可調用的對象五花八門。
C++11打破了這個尷尬,統一可調用對象的概念,可調用對象有以下幾種定義:
-
是一個函數指針,參考 C++ 函數指針和函數類型;
-
是一個具有operator()成員函數的類的對象;
-
可被轉換成函數指針的類對象;
-
一個類成員函數指針;
C++中可調用對象的雖然都有一個比較統一的操作形式,但是定義方法五花八門,這樣就導致使用統一的方式保存可調用對象或者傳遞可調用對象時,會十分繁瑣。C++11中提供了std::function和std::bind統一了可調用對象的各種操作。
舉個例子:
// 普通函數int add(int a, int b){return a+b;} // lambda表達式auto mod = [](int a, int b){ return a % b;} // 函數對象類struct divide{ int operator()(int denominator, int divisor){ return denominator/divisor; }};上述三種可調用對象雖然類型不同,但是共享了一種調用形式:
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();總結一下:
std::function是一個類模版,是一種通用、多態的函數封裝。
std::function對象可以對普通函數、Lambda表達式、函數指針、以及其它函數對象等 進行存儲、復制、和調用操作。
std::function對象是對可調用實體類型安全的包裹(我們知道像函數指針這類可調用實體,是類型不安全的)。
std::function通常是一個函數對象類,它包裝其它任意的函數對象,被包裝的函數對象具有類型為T1, …,TN的N個參數,并且返回一個可轉換到R類型的值。
std::function使用 模板轉換構造函數接收被包裝的函數對象;特別是,閉包類型可以隱式地轉換為std::function。
最簡單的理解就是:
通過std::function對可調用實體(普通函數、Lambda表達式、函數指針、以及其它函數對象等)的封裝,形成一個新的可調用的std::function對象;讓我們不再糾結那么多的可調用實體
下邊貼段代碼加深一下理解:
#include <functional>#include <iostream>using namespace std; std::function< int(int) > Functional; // 普通函數int TestFunc(int a){ return a;} // Lambda表達式auto lambda = [](int a)->int{ return a; }; // 仿函數(functor)class Functor{public: int operator()(int a){ return a; }}; // 1.類成員函數// 2.類靜態函數class TestClass{public: int ClassMember(int a) { return a; } static int StaticMember(int a) { return a; }}; int main(){ // 普通函數 Functional = TestFunc; int result = Functional(10); cout << "普通函數:"<< result << endl; // Lambda表達式 Functional = lambda; result = Functional(20); cout << "Lambda表達式:"<< result << endl; // 仿函數 Functor testFunctor; Functional = testFunctor; result = Functional(30); cout << "仿函數:"<< result << endl; // 類成員函數 TestClass testObj; Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1); result = Functional(40); cout << "類成員函數:"<< result << endl; // 類靜態函數 Functional = TestClass::StaticMember; result = Functional(50); cout << "類靜態函數:"<< result << endl; return 0;}總結
以上是生活随笔為你收集整理的lambda 和 std::function的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动语义、完美转发
- 下一篇: std::mutex