日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

Effective Modern C++:06lambda表达式

發布時間:2024/4/15 c/c++ 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Effective Modern C++:06lambda表达式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

???????? lambda表達式實際上是語法糖,任何lambda表達式能做到的,手動都能做到,無非是多打幾個字。但是lambda作為一種創建函數對象的手段,實在太過方便,自從有了lambda表達式,使用復雜謂詞來調用STL中的”_if”族算法(std::find_if,std::remove_if等)變得非常方便,這種情況同樣發生在比較函數的算法族上。在標準庫之外,lambda表達式可以臨時制作出回調函數、接口適配函數或是語境相關函數的特化版本以供一次性調用。下面是關于lambda相關術語的提醒:

???????? lambda表達式,是表達式的一種,比如下面代碼中紅色的就是lambda表達式:

std::find_if(container.begin(), container.end(), [](int val) { return 0 < val && val < 10; });

?? ? ? ? ?閉包,是lambda表達式創建的運行期對象,在上面對std::find_if的調用中,閉包就是作為第三個實參在運行期傳遞給std::find_if的對象。

???????? 閉包類,是實例化閉包的類,每個lambda表達式都會觸發編譯器生成一個獨一無二的閉包類,而lambda表達式中的語句會變成閉包類成員函數的可執行指令。

????????

閉包可以復制,所以,對應于單獨一個lambda表達式的閉包類型可以有多個閉包:

int x; // x is local variable auto c1 = [x](int y) { return x * y > 55; }; // c1 is copy of the closure produced by the lambda auto c2 = c1; // c2 is copy of c1 auto c3 = c2; // c3 is copy of c2

?? ? ? ? ?c1、c2和c3都是同一個lambda表達式產生的閉包的副本。

???????? 在非正式場合,lambda表達式,閉包和閉包類之間的界限可以模糊一些。但是在下面的條款中,需要能區別哪些存在于編譯期(lambda表達式和閉包類),哪些存在于運行期(閉包),以及它們之間的相互聯系。

?

31:避免默認捕獲模式

???????? C++11中有兩種默認捕獲模式:按引用或按值。按引用的默認捕獲模式可能導致空懸引用,而按值的默認捕獲模式可能會讓你覺得不存在空懸引用的問題(實際上不是)。

???????? 按引用捕獲會導致閉包包含指向局部變量(或形參)的引用,一旦由lambda表達式所創建的閉包的生命期超過了該局部變量或形參的生命期,那么閉包內的引用就會空懸,比如下面的代碼:

using FilterContainer = std::vector<std::function<bool(int)>>; FilterContainer filters; // filtering funcsvoid addDivisorFilter() {auto calc1 = computeSomeValue1();auto calc2 = computeSomeValue2();auto divisor = computeDivisor(calc1, calc2);filters.emplace_back([&](int value) { return value % divisor == 0; }); }

?? ? ? ? ?這段代碼隨時會出錯,lambda中按引用捕獲了局部變量divisor,但當addDivisorFilter函數返回時局部變量被銷毀,使用filters就會產生未定義行為。

?

???????? 如果不這樣做,使用顯式方式按引用捕獲divisor,問題依然存在:

filters.emplace_back([&divisor](int value) { return value % divisor == 0; } );

?? ? ? ? ?但是通過顯示捕獲,就比較容易看出lambda表達式的生存依賴于divisor的生命期。顯式的寫出”divisor”可以提醒我們要保證divisor至少應該和lambda具有一樣長的生命期,這要比[&]這種所傳達的不痛不癢的“要保證沒有空懸引用”式的勸告更讓人印象深刻。

?

???????? 如果知道閉包會立即使用(比如傳遞給STL算法)且不會被復制,這種情況下,你可能會爭論說,既然沒有空懸引用的風險,也就沒有必要避免使用默認引用捕獲模式。但是從長遠觀點來看,顯示的列出lambda表達式所依賴的局部變量或形參,是更好的軟件工程實踐。

?

???????? 上面的例子中,解決問題的一種辦法是對divisor采用按值的默認捕獲模式:

filters.emplace_back([=](int value) { return value % divisor == 0; } );

?? ? ? ? ?對于這個例子而言,這樣做確實是沒問題的。但是按值的默認捕獲并非一定能避免空懸引用,問題在于如果按值捕獲了一個指針,在lambda表達式創建的閉包中持有的是這個指針的副本,但是沒有辦法阻止lambda表達式之外的代碼針對該指針實施delete操作導致的指針副本空懸。比如下面的代碼:

class Widget { public:… // ctors, etc.void addFilter() const; // add an entry to filters private:int divisor; // used in Widget's filter };void Widget::addFilter() const {filters.emplace_back([=](int value) { return value % divisor == 0; }); }

?? ? ? ? ?這樣的代碼看起來安全,然而實際上卻是大錯特錯的。捕獲只能針對于在創建lambda表達式的作用域內可見的非靜態局部變量(包括形參),而在Widget::addFilter函數體內,divisor并非局部變量,而是Widget類的成員變量,它根本沒辦法捕獲。這么一來,如果不使用默認捕獲模式,代碼就不會通過編譯:

void Widget::addFilter() const {filters.emplace_back([](int value) { return value % divisor == 0; }); }

?? ? ? ? ?而且,如果試圖顯示捕獲divisor(無論是按值還是按引用),這個捕獲語句都不能通過編譯,因為divisor既不是局部變量,也不是形參:

void Widget::addFilter() const {filters.emplace_back([divisor](int value) { return value % divisor == 0; }); }

?? ? ? ? ?但是為什么一開始的代碼沒有發生編譯錯誤呢?this指針是關鍵所在,每一個非靜態成員函數都持有一個this指針,每當提及該類的成員變量時都會用到這個指針。比如在Widget的任何成員函數中,編譯器內部都會把divisor替換成this->divisor。因此,在Widget::addFilter的按值默認捕獲版本中,被捕獲的實際上是Widget的this指針,而不是divisor。從編譯器的角度來看,實際的代碼相當于:

void Widget::addFilter() const {auto currentObjectPtr = this;filters.emplace_back([currentObjectPtr](int value) { return value % currentObjectPtr->divisor == 0; }); }

?? ? ? ? ?因此,該lambda閉包的存活,與它含有this指針指向的Widget對象的生命期是綁在一起的,比如下面的代碼:

using FilterContainer = std::vector<std::function<bool(int)>>; FilterContainer filters; void doSomeWork() {auto pw = std::make_unique<Widget>(); pw->addFilter();… }

?? ? ? ? ?當調用doSomeWork時創建了一個篩選函數,它依賴于std::make_unique創建的Widget對象,該函數被添加到filters中,然而當doSomeWork結束后,Widget對象隨著std::unique_ptr的銷毀而銷毀,從那一刻起,filters中就含有了一個帶有空懸指針的元素。

???????? 這一問題可以通過將想捕獲的成員變量復制到局部變量中,而后捕獲該局部變量的部分得意解決:

void Widget::addFilter() const {auto divisorCopy = divisor;filters.emplace_back([divisorCopy](int value) { return value % divisorCopy == 0; } ); }

??

???????? 在C++14中,捕獲成員變量的一種更好的方式是使用廣義lambda捕獲(generalized lambda):

void Widget::addFilter() const {filters.emplace_back([divisor = divisor](int value) { return value % divisor == 0; }); }

?? ? ? ? ?對廣義lambda捕獲而言,沒有默認捕獲模式一說,但是,就算在C++14中,本條款的建議,避免使用默認捕獲模式依然成立。

?

???????? 使用按值默認捕獲的另一個缺點,在于它似乎表明閉包是自治的,與閉包外的數據變化絕緣,然而作為一般性的結論,這是不正確的。因為lambda表達式可能不僅依賴于局部變量或形參,他還可能依賴于靜態存儲期對象,這樣的對象定義在全局或名字空間作用域中,或是在類,函數,文件中以static飾詞聲明。這樣的對象可以在lambda內使用,但是它們不能被捕獲。如果使用了按值默認捕獲模式,這些對象就會給人以錯覺,認為它們可以加以捕獲:

void addDivisorFilter() {static auto calc1 = computeSomeValue1(); static auto calc2 = computeSomeValue2(); static auto divisor = computeDivisor(calc1, calc2);filters.emplace_back([=](int value) { return value % divisor == 0; });++divisor; }

?? ? ? ? ?看到[=]就認為lambda復制了它內部使用的對象,得出lambda是自治的這種結論,是錯誤的。實際上該lambda表達式并不獨立,它沒有使用任何的非靜態局部變量或形參,所以它沒能捕獲任何東西。更糟糕的是lambda表達式的代碼中使用了靜態變量divisor,每次調用addDivisorFilter后,divisor會遞增,使得添加到filters中的每個lambda表達式的行為都不一樣。如果一開始就避免使用按值的默認捕獲模式,也就能消除代碼被誤讀的風險了。

?

?

32:使用初始化捕獲將對象移入閉包

???????? 有時按值捕獲和按引用捕獲并不能滿足所有的需求。比如想要把move-only對象(如std::unique_ptr或std::future)放入閉包,或者想把復制昂貴而移動低廉的對象移入閉包時,C++11沒有提供可行的方法,但是C++14為對象移動提供了直接支持。

???????? 實際上,C++14提供了一種全新的捕獲方式,按移動的捕獲只不過是該機制能夠實現的多種效果之一罷了。這種方式稱為初始化捕獲(init capture),它可以做到C++11的捕獲形式所有能夠做到的事情(除了默認捕獲模式,而這是需要遠離的),不過初始化捕獲的語法稍顯啰嗦,如果C++11的捕獲能解決問題,則大可以使用之。

???????? 下面是初始化捕獲實現移入捕獲的例子:

class Widget { public:bool isValidated() const;bool isProcessed() const;bool isArchived() const; private:… }; auto pw = std::make_unique<Widget>(); … auto func = [pw = std::move(pw)] { return pw->isValidated() && pw->isArchived(); };

?? ? ? ? ?上面的例子中,位于”=”左側的pw,是lambda創建的閉包類中成員變量的名字;而位于”=”右側的是其初始化表達式,所以”pw=std::move(pw)”表達了在閉包類中創建一個成員變量pw,然后使用針對局部變量pw實施std::move的結果來初始化該成員變量。在lambda內部使用pw也是指的閉包類的成員變量。一旦定義lambda表達式之后,因為局部變量pw已經被move了,所以其不再掌握任何資源。

???????? 上面的例子還可以不使用局部變量pw:

auto func = [pw = std::make_unique<Widget>()]{ return pw->isValidated() && pw->isArchived(); };

?? ? ? ? ?這種捕獲方式在C++14中還稱為廣義lambda捕獲(generalized lambda capture)。

?

???????? 但是如果編譯器尚不支持C++14,則該如何實現按移動捕獲呢?要知道一個lambda表達式不過是生成一個類并創建一個該類的對象的手法罷了,并不存在lambda能做而手工不能做的事情,上面C++14的例子,如果使用C++11,可以寫為:

class IsValAndArch { public: using DataType = std::unique_ptr<Widget>;explicit IsValAndArch(DataType&& ptr) : pw(std::move(ptr)) {}bool operator()() const{ return pw->isValidated() && pw->isArchived(); } private:DataType pw; }; auto func = IsValAndArch(std::make_unique<Widget>());

?? ? ? ? ?這種寫法要比使用lambda麻煩很多。

如果非要使用lambda實現按移動捕獲,也不是全無辦法,可以借助std::bind實現:把要捕獲的對象移動到std::bind產生的函數對象中;給lambda表達式一個指向欲捕獲的對象的引用。比如C++14中的寫法:

std::vector<double> data; … // populate data auto func = [data = std::move(data)]{ /* uses of data */ };

?? ? ? ? ?如果采用C++11中使用std::bind和lambda的寫法,等價代碼如下:

std::vector<double> data; … // as above auto func = std::bind([](const std::vector<double>& data) { /* uses of data */ },std::move(data) );

?? ? ? ? ?std::bind也生成函數對象,可以將它生成的對象稱為綁定對象。std::bind的第一個實參是個可調用對象,接下來的所有實參表示傳遞給該對象的值。

???????? 綁定對象內含有傳遞給std::bind所有實參的副本。對于左值實參,綁定對象內對應的副本實施的是復制構造;對于右值實參,實施的是移動構造。上面的例子中,第二個實參是個右值,所以在綁定對象內,使用局部變量data移動構造其副本,這種移動構造動作正是實現模擬移動捕獲的關鍵所在,因為把右值移入綁定對象,正是繞過C++11無法將右值移動到閉包的手法。

???????? 當一個綁定對象被調用時,它所存儲的實參會傳遞給std::bind的那個可調用對象,也就是func被調用時,func內經由移動構造得到的data副本就會作為實參傳遞給那個原先傳遞給std::bind的lambda表達式。這個C++11寫法比C++14多了一個形參data,該形參是個指向綁定對象內部的data副本的左值引用,這么一來,在lambda內對data形參所做的操作,都會實施在綁定對象內移動構造而得的data副本之上,與原局部變量data無關。

?

???????? 默認情況下,lambda閉包類中的operator()成員函數會帶有const飾詞,因此閉包里的所有成員變量在lambda表達式的函數體內都帶有const飾詞,但綁定對象內移動構造而得的data副本并不帶有const飾詞,所以為了防止該data部分在lambda表達式內被意外修改,lambda的形參就聲明為常量引用。但是如果lambda表達式帶有mutable飾詞,則閉包中的operator()函數就不會在聲明時帶有const飾詞,相應的做法就是在lambda聲明中略去const:

auto func = std::bind([](std::vector<double>& data) mutable { /* uses of data */ },std::move(data) );

?? ? ? ? ?綁定對象存儲著傳遞給std::bind所有實參的副本,因此本例中的綁定對象就包含一份由第一個實參lambda表達式產生的閉包的副本。這么一來,該閉包的生命期就和綁定對象是相同的。

?

???????? 另外一個例子,下面是C++14的代碼:

auto func = [pw = std::make_unique<Widget>()] { return pw->isValidated() && pw->isArchived(); };

?? ? ? ? ?如果使用C++11采用bind的寫法:

auto func = std::bind([](const std::unique_ptr<Widget>& pw){ return pw->isValidated() && pw->isArchived(); },std::make_unique<Widget>() );

??

?

33:要對auto&&類型的形參使用std::forward,則需要使用decltype

???????? 泛型lambda表達式(generic lambda)是C++14最振奮人心的特性之一:lambda表達式的形參列表中可以使用auto,它的實現直截了當,閉包類中的operator()采用模板實現。比如下面的lambda表達式,以及其對應的實現:

auto f = [](auto x){ return func(normalize(x)); };class SomeCompilerGeneratedClassName { public:template<typename T> auto operator()(T x) const{ return func(normalize(x)); }… };

?? ? ? ? ?這個例子中,lambda表達式對x的動作就是將其轉發給normalize,如果normalize區別對待左值和右值,則該lambda表達式的實現是有問題的,正確的寫法應該是使用萬能引用并將其完美轉發給normalize:

auto f = [](auto&& x) { return func(normalize(std::forward<???>(x))); };

?? ? ? ? ?這里的問題是,std::forward的模板實參”???”應該怎么寫?

這里可以使用decltype(x),但是decltype(x)產生的結果,卻與std::forward的使用慣例有所不同。如果傳入的是個左值,則x的類型是左值引用,decltype(x)得到的也是左值引用;如果傳入的是右值,則x的類型是右值引用,decltype(x)得到的也是右值引用,但是,std::forward的使用慣例是std::forward<T>,其中T要么是個左值引用,要么是個非引用。

???????? 再看一下條款28中std::forward的簡單實現:

template<typename T> T&& forward(remove_reference_t<T>& param) {return static_cast<T&&>(param); }

?? ? ? ? ?如果客戶代碼想要完美轉發Widget類型的右值,則按照慣例它應該采用Wdiget類型,而非引用類型來實例化std::forward,然后std::forard模板實例化結果是:

Widget&& forward(Widget& param) { return static_cast<Widget&&>(param); }

?? ? ? ? ?如果使用右值引用實例化T,也就是Widget&&實例化T,得到的結果是:

Widget&& && forward(Widget& param) {return static_cast<Widget&& &&>(param); }

?? ? ? ? ?實施了引用折疊之后:

Widget&& forward(Widget& param) {return static_cast<Widget&&>(param); }

?? ? ? ? ?經過對比,發現這個版本和T為Widget時的std::forward是完全一樣的,因此,實例化std::forward時,使用一個右值引用和使用非引用類型,結果是相同的。所以,我們的完美轉發lambda表達式如下:

auto f =[](auto&& param){return func(normalize(std::forward<decltype(param)>(param)));};

?? ? ? ? ?稍加改動,就可以得到能接收多個形參的完美轉發lambda式版本,因為C++14中的lambda能夠接受變長形參:

auto f =[](auto&&... params){return func(normalize(std::forward<decltype(params)>(params)...));};

?

??

34:優先使用lambda表達式,而非std::bind

???????? std::bind在2005年就已經是標準庫的組成部分了(std::tr1::bind),這意味著std::bind已經存在了十多年了,你可能不太愿意放棄這么一個運作良好的工具,然而有時候改變也是有益的,因為在C++11中,相對于std::bind,lambda幾乎總會是更好的選擇,而到了C++14,lambda簡直已成了不二之選。

???????? lambda表達式相對于std::bind的優勢,最主要的是其具備更高的可讀性:

// typedef for a point in time (see Item 9 for syntax) using Time = std::chrono::steady_clock::time_point; // see Item 10 for "enum class" enum class Sound { Beep, Siren, Whistle }; // typedef for a length of time using Duration = std::chrono::steady_clock::duration; // at time t, make sound s for duration d void setAlarm(Time t, Sound s, Duration d);// setSoundL ("L" for "lambda") is a function object allowing a // sound to be specified for a 30-sec alarm to go off an hour // after it's set auto setSoundL = [](Sound s){// make std::chrono components available w/o qualificationusing namespace std::chrono;setAlarm(steady_clock::now() + hours(1), s, seconds(30));};

?? ? ? ? ?這里的lambda表達式,即使是沒什么經驗的讀者也能看出來,傳遞給lambda的形參會作為實參傳遞給setAlarm。到了C++14中,C++14提供了秒,毫秒和小時的標準字面值,所以,可以寫成這樣:

auto setSoundL = [](Sound s) {using namespace std::chrono;using namespace std::literals;setAlarm(steady_clock::now() + 1h, s, 30s); };

?? ? ? ? ?而下面的代碼是使用std::bind的等價版本,不過實際上它還有一處錯誤的,后續在解決這個錯誤:

using namespace std::chrono; using namespace std::literals; using namespace std::placeholders; // needed for use of "_1" auto setSoundB = // "B" for "bind"std::bind(setAlarm, steady_clock::now() + 1h, _1, 30s);

?? ? ? ? ?對于初學者而言,占位符”_1”簡直好比天書,而即使是行家也需要腦補出從占位符數字到它在std::bind形參列表中的位置映射關系,才能理解在調用setSoundB時傳入的第一個實參,會作為第二個實參傳遞給setAlarm。該實參的類型在std::bind的調用過程中是未加識別的,所以還需要查看setAlarm的聲明才能決定應該傳遞何種類型的實參到setSoundB。

???????? 這段代碼的錯誤之處在于,在lambda表達式中,表達式”steady_clock::now() + 1h”是setAlarm的實參之一,這一點清清楚楚,該表達式會在setAlarm被調用時求值,這樣是符合需求的,就是需要在setAlarm被調用的時刻之后的一個小時啟動報警。但是在std::bind中,”steady_clock::now() + 1h”作為實參傳遞給std::bind,而非setAlarm,該表達式在調用std::bind時就進行求值了,并且求得的結果會存儲在綁定對象中,這導致的結果是報警的啟動時刻是在std::bind調用之后的一個小時,而非setAlarm調用之后的一個小時。

???????? 要解決這個問題,就需要std::bind延遲表達式的求值到調用setAlarm的時刻,實現這一點,就是需要嵌套第二層std::bind的調用:

auto setSoundB =std::bind(setAlarm,std::bind(std::plus<>(), steady_clock::now(), 1h),_1,30s);

?? ? ? ? ?在C++14中,標準運算符模板的模板類型實參大多數情況下可以省略不寫,所以此處也沒必要在std::plus中提供了,而C++11中還沒有這樣的特性,所以在C++11中,想要實現上面的代碼,只能是:

using namespace std::chrono; // as above using namespace std::placeholders; auto setSoundB =std::bind(setAlarm,std::bind(std::plus<steady_clock::time_point>(), steady_clock::now(), hours(1)),_1,seconds(30));

??

???????? 如果對setAlarm實施了重載,則又會有新的問題:

enum class Volume { Normal, Loud, LoudPlusPlus }; void setAlarm(Time t, Sound s, Duration d, Volume v); auto setSoundL = [](Sound s) {using namespace std::chrono;setAlarm(steady_clock::now() + 1h, s, 30s); };

?? ? ? ? ?即使有了重載,lambda表達式依然能正常工作,重載決議會選擇有三個參數版本的setAlarm。但是到了std::bind,就沒辦法通過編譯了:

auto setSoundB = std::bind(setAlarm, std::bind(std::plus<>(),steady_clock::now(),1h),_1,30s);

?? ? ? ? ?這是因為編譯器無法確定應該將哪個setalarm傳遞給set::bind,它拿到的所有信息只有一個函數名。為了使std::bind能夠通過編譯,setAlarm必須強制轉換到適當的函數指針類型:

using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d); auto setSoundB = std::bind(static_cast<SetAlarm3ParamType>(setAlarm),std::bind(std::plus<>(), steady_clock::now(), 1h),_1,30s);

?? ? ? ? ?但是這么做又帶來了lambda和std::bind的另一個不同之處。在lambda生成的setSoundL的函數調用運算符中,調用setAlarm采用的是常規函數喚起方式,這么一來,編譯器就可以用慣常的手法將其內聯:

setSoundL(Sound::Siren); // body of setAlarm may well be inlined here

?? ? ? ? ?而std::bind調用中使用了函數指針,這意味著在setSoundB的函數調用運算符中,setAlarm是通過函數指針來調用的,編譯器一般無法將函數指針發起的函數調用進行內聯,所以lambda表達式就有可能生成比std::bind更快的代碼。

?

???????? 在setAlarm例子中,僅僅涉及了函數的調用而已,如果你想做的事比這更復雜,則lambda表達式的優勢則更加明顯。比如:

auto betweenL =[lowVal, highVal](const auto& val){ return lowVal <= val && val <= highVal; };

?? ? ? ? ?這里的lambda使用了捕獲。std::bind要想要實現同樣的功能,必須用比較晦澀的方式來構造代碼,下面分別是C++14和C++11的寫法:

using namespace std::placeholders; auto betweenB =std::bind(std::logical_and<>(),std::bind(std::less_equal<>(), lowVal, _1),std::bind(std::less_equal<>(), _1, highVal));auto betweenB = std::bind(std::logical_and<bool>(),std::bind(std::less_equal<int>(), lowVal, _1),std::bind(std::less_equal<int>(), _1, highVal));

?? ? ? ? ?還是需要使用std::bind的延遲計算方法。

????????

???????? 再看下面的代碼:

enum class CompLevel { Low, Normal, High }; Widget compress(const Widget& w, CompLevel lev); //make compressedcopy of w Widget w; using namespace std::placeholders; auto compressRateB = std::bind(compress, w, _1);

?? ? ? ? ?這里的w傳遞給std::bind時,是按值存儲在std::bind生成的對象中的,在std::bind的調用中,按值還是按引用存儲只能是牢記規則。std::bind總是復制其實參,但是調用方可以通過對實參實施std::ref的方法達到按引用存儲的效果,因此:

auto compressRateB = std::bind(compress, std::ref(w), _1);

?結果就是compressRateB的行為如同持有的是個指向w的引用,而非其副本。

而在lambda中,w無論是按值還是按引用捕獲,代碼中的書寫方式都很明顯:

auto compressRateL = [w](CompLevel lev) { return compress(w, lev); };

?? ? ? ? ?同樣明顯的還有形參的傳遞方式:

compressRateL(CompLevel::High); // arg is passed by value compressRateB(CompLevel::High); // how is arg passed?

?? ? ? ? ?Lambda返回的閉包中,很明顯實參是按值傳遞給lev的;而在std::bind返回綁定對象中,形參的傳遞方式是什么呢?這里也只能牢記規則,綁定對象的所有實參都是按引用傳遞的,因為此種對象的函數調用運算符使用了完美轉發。

????????

???????? 總而言之,lambda表達式要比std::bind可讀性更好,表達能力更強,運行效率也可能更好,在C++14中,幾乎沒有std::bind的適當用例,而在C++11中,std::bind僅在兩個受限場合還算有使用的理由:

???????? 移動捕獲,C++11沒有提供移動捕獲的語法,參考上一條款;

? ? ? ? ?多態函數對象,因為綁定對象的函數調用運算符使用了完美轉發,所以可以接收任何類型的實參,因此當需要綁定的對象具有一個函數調用運算符模板時,是有利用價值的:

class PolyWidget { public:template<typename T>void operator()(const T& param);… };PolyWidget pw; auto boundPW = std::bind(pw, _1); boundPW(1930); // pass int to PolyWidget::operator() boundPW(nullptr); // pass nullptr to PolyWidget::operator() boundPW("Rosebud"); // pass string literal to PolyWidget::operator()

?? ? ? ? ?C++11中的lambda表達式沒有辦法實現這一點,但是在C++14中,使用帶有auto類型形參的lambda表達式可以很容易的實現這一點:

auto boundPW = [pw](const auto& param) { pw(param); };

?

轉載于:https://www.cnblogs.com/gqtcgq/p/9937013.html

總結

以上是生活随笔為你收集整理的Effective Modern C++:06lambda表达式的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。