您好,登錄后才能下訂單哦!
lambda 表達式和右值引用是 C++11 的兩個非常有用的特性。
lambda 表達式實際上會由編譯器創建一個 std::function
對象,以值的方式捕獲的變量則會由編譯器復制一份,在 std::function 對象中創建一個對應的類型相同的 const 成員變量,如下面的這段代碼:
int main(){ std::string str = "test"; printf("String address %p in main, str %s\n", &str, str.c_str()); auto funca = [str]() { printf("String address %p (main lambda), str %s\n", &str, str.c_str()); }; std::function<void()> funcb = funca; std::function<void()> funcc; funcc = funca; printf("funca\n"); funca(); std::function<void()> funcd = std::move(funca); printf("funca\n"); funca(); printf("funcb\n"); funcb(); std::function<void()> funce; funce = std::move(funcb); printf("funcb\n"); // funcb(); printf("funcc\n"); funcc(); printf("funcd\n"); funcd(); printf("funce\n"); funce(); // std::function<void(int)> funcf = funce; return 0; }
這段代碼的輸出如下:
Stringaddress0x7ffd9aaab720 in main, strtest
funca
Stringaddress0x7ffd9aaab740 (main lambda), strtest
funca
Stringaddress0x7ffd9aaab740 (main lambda), str
funcb
Stringaddress0x55bdd2160280 (main lambda), strtest
funcb
funcc
Stringaddress0x55bdd21602b0 (main lambda), strtest
funcd
Stringaddress0x55bdd21602e0 (main lambda), strtest
funce
Stringaddress0x55bdd2160280 (main lambda), strtest
由上面調用 funca 時的輸出,可以看到 lambda 表達式以值的方式捕獲的對象 str,其地址在 lambda 表達式內部和外部是不同的。
std::function
類對象和普通的魔板類對象一樣,可以拷貝構造,如:
std::function<void()> funcb = funca;
由調用 funcb 時的輸出,可以看到拷貝構造時是做了逐成員的拷貝構造。
std::function
類對象可以賦值,如:
std::function<void()> funcc; funcc = funca;
由調用 funcc 時的輸出,可以看到賦值時是做了逐成員的賦值。
std::function
類對象可以移動構造,如:
std::function<void()> funcd = std::move(funca);
由移動構造之后,調用 funca 和 funcd 時的輸出,可以看到移動構造時是做了逐成員的移動構造。
std::function
類對象可以移動賦值,如:
std::function<void()> funce; funce = std::move(funcb); printf("funcb\n"); // funcb();
這里把移動賦值之后對 funcb 的調用注釋掉了,這是因為,作為源的 funcb 在移動賦值之后被調用是,會拋出異常,如:
String address 0x562334c34280 (main lambda), str test funcb terminate called after throwing aninstanceof 'std::bad_function_call' what(): bad_function_call
同時,由調用 funce 時的輸出可以看到,該輸出與 funcb 在移動賦值之前被調用時的輸出完全相同。 即移動賦值是將對象整體 move 走了,這與移動構造時的行為不太一樣。
std::function
類對象的拷貝構造或者賦值,也需要滿足類型匹配原則,如:
std::function<void(int)> funcf = funce;
這行代碼會造成編譯失敗,編譯錯誤信息如下:
../src/DemoTest.cpp: In function ‘intmain()':
../src/DemoTest.cpp:64:36: error: conversion from ‘std::function<void()>' to non-scalar type ‘std::function<void(int)>' requested
std::function<void(int)> funcf = funce;
^~~~~
make: *** [src/DemoTest.o] Error 1
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
在 lambda 中以值的方式捕獲的右值對象,只是在 lambda 的 std::function 對象中做了一份被捕獲的右值對象的拷貝,而原來的右值則沒有任何改變。
接下來再來看一段示例代碼:
#include<iostream> #include<functional> #include<string> using namespace std; void funcd(std::string&&str){ printf("String address %p in funcd A, str %s\n", &str, str.c_str()); string strs = std::move(str); printf("String address %p in funcd B, str %s, strs %s\n", &str, str.c_str(), strs.c_str()); } void funcc(std::stringstr){ printf("String address %p in funcc, str %s\n", &str, str.c_str()); } void funcb(std::string&str){ printf("String address %p in funcb, str %s\n", &str, str.c_str()); } void funca(std::string&&str){ printf("String address %p in funca A, str %s\n", &str, str.c_str()); std::string stra = str; printf("String address %p in funca B, str %s, stra %s\n", &str, str.c_str(), stra.c_str()); } int main(){ std::string str = "test"; printf("String address %p in main A, str %s\n", &str, str.c_str()); funca(std::move(str)); printf("String address %p in main B, str %s\n", &str, str.c_str()); // funcb(std::move(str)); printf("String address %p in main C, str %s\n", &str, str.c_str()); funcc(std::move(str)); printf("String address %p in main D, str %s\n", &str, str.c_str()); std::string stra = "testa"; printf("String address %p in main E, stra %s\n", &stra, stra.c_str()); funcd(std::move(stra)); printf("String address %p in main F, stra %s\n", &stra, stra.c_str()); return 0; }
上面這段代碼在執行時,輸出如下:
String address 0x7ffc833f4660 in main A, str test
String address 0x7ffc833f4660 in funca A, str test
String address 0x7ffc833f4660 in funca B, str test, stra test
String address 0x7ffc833f4660 in main B, str test
String address 0x7ffc833f4660 in main C, str test
String address 0x7ffc833f4680 in funcc, str test
String address 0x7ffc833f4660 in main D, str
String address 0x7ffc833f4680 in main E, stra testa
String address 0x7ffc833f4680 in funcd A, str testa
String address 0x7ffc833f4680 in funcd B, str , strs testa
String address 0x7ffc833f4680 in main F, stra
funca 函數接收右值引用作為參數,由 funca 函數內部及函數調用前后的輸出可以看到, std::move()
本身什么都沒做,單單調用 std::move()
并不會將原來的對象的內容移動到任何地方。 std::move()
只是一個簡單的強制類型轉換,將左值轉為右值引用。同時可以看到,用右值引用作為參數構造對象,也并沒有對右值引用所引用的對象產生任何影響。
funcb 函數接收左值引用作為參數,上面的代碼中,如下這一行注釋掉了:
// funcb(std::move(str));
這是因為, funcb 不能用一個右值引用作為參數來調用。用右值引用作為參數,調用接收左值引用作為參數的函數 funcb 時,會編譯失敗:
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/DemoTest.d" -MT"src/DemoTest.o" -o "src/DemoTest.o" "../src/DemoTest.cpp"
../src/DemoTest.cpp: In function ‘int main()':
../src/DemoTest.cpp:34:18: error: cannot bind non-const lvalue reference of type ‘std::__cxx11::string& {aka std::__cxx11::basic_string<char>&}' to an rvalue of type ‘std::remove_reference<std::__cxx11::basic_string<char>&>::type {aka std::__cxx11::basic_string<char>}'
funcb(std::move(str));
~~~~~~~~~^~~~~
../src/DemoTest.cpp:17:6: note: initializing argument 1 of ‘void funcb(std::__cxx11::string&)'
void funcb(std::string &str) {
^~~~~
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
make: *** [src/DemoTest.o] Error 1
不過,如果 funcb 接收 const 左值引用作為參數,如 void funcb(const std::string &str)
,則在調用該函數時,可以用右值引用作為參數,此時 funcb 的行為與 funca 基本相同。
funcc 函數接收左值作為參數,由 funcc 函數內部及函數調用前后的輸出可以看到,由于有了左值作為接收者,傳入的右值引用所引用的對象的值被 move 走,進入函數的參數棧對象中了。
funcd 函數與 funca 函數一樣,接收右值引用作為參數,但 funcd 的特別之處在于,在函數內部,右值構造了一個新的對象,因而右值引用原來引用的對象的值被 move 走,進入了新構造的對象中。
再來看一段示例代碼:
#include<iostream> #include<functional> #include<string> using namespace std; void bar(std::string&&str){ printf("String address %p in bar A, str %s\n", &str, str.c_str()); string strs = std::move(str); printf("String address %p in bar B, str %s, strs %s\n", &str, str.c_str(), strs.c_str()); } std::function<void()> bar_bar(std::string &&str) { auto funf = [&str]() { printf("String address %p (foo lambda) F, stra %s\n", &str, str.c_str()); }; return funf; } std::function<void()> foo(std::string &&str) { printf("String address %p in foo A, str %s\n", &str, str.c_str()); // auto funa = [str]() { // printf("String address %p (foo lambda) A, str %s\n", &str, str.c_str()); // bar(str); // }; // funa(); // // auto funb = [str]() { // printf("String address %p (foo lambda) B, str %s\n", &str, str.c_str()); // bar(std::move(str)); // }; // funb(); // auto func = [str]() mutable { // printf("String address %p (foo lambda) C, str %s\n", &str, str.c_str()); // bar(str); // }; // func(); auto fund = [str]() mutable { printf("String address %p (foo lambda) D, str %s\n", &str, str.c_str()); bar(std::move(str)); }; fund(); auto fune = [&str]() { printf("String address %p (foo lambda) E, str %s\n", &str, str.c_str()); bar(std::move(str)); }; fune(); std::string stra = "testa"; return bar_bar(std::move(stra)); } int main(){ std::string str = "test"; printf("String address %p in main A, str %s\n", &str, str.c_str()); auto funcg = foo(std::move(str)); printf("String address %p in main B, str %s\n", &str, str.c_str()); funcg(); return 0; }
上面這段代碼的輸出如下:
Stringaddress0x7ffc9fe7c5c0 in main A, strtest
Stringaddress0x7ffc9fe7c5c0 in foo A, strtest
Stringaddress0x7ffc9fe7c540 (foo lambda) D, strtest
Stringaddress0x7ffc9fe7c540 in barA, strtest
Stringaddress0x7ffc9fe7c540 in barB,str, strstest
Stringaddress0x7ffc9fe7c5c0 (foo lambda) E, strtest
Stringaddress0x7ffc9fe7c5c0 in barA, strtest
Stringaddress0x7ffc9fe7c5c0 in barB,str, strstest
Stringaddress0x7ffc9fe7c5c0 in main B,str
Stringaddress0x7ffc9fe7c560 (foo lambda) F, stra����
在函數 foo() 中定義的 funa 及對 funa 的調用被注釋掉了,這是因為這段代碼會導致編譯失敗,具體的錯誤信息如下:
Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/DemoTest.d" -MT"src/DemoTest.o" -o "src/DemoTest.o" "../src/DemoTest.cpp"
../src/DemoTest.cpp: In lambda function:
../src/DemoTest.cpp:25:12: error: cannot bind rvalue reference of type ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}' to lvalue of type ‘const string {aka const std::__cxx11::basic_string<char>}'
bar(str);
^
../src/DemoTest.cpp:7:6: note: initializing argument 1 of ‘void bar(std::__cxx11::string&&)'
void bar(std::string &&str) {
^~~
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
make: *** [src/DemoTest.o] Error 1
如我們前面提到的,在 lambda 表達式中,以值的方式捕獲右值引用時,會在編譯器為該 lambda 表達式生成的 std::function 類中生成一個 const 對象,const 對象是不能作為右值引用來調用接收右值引用為參數的函數的。
在函數 foo() 中定義的 funb ,相對于 funa ,在調用 bar() 時,為 str 裹上了 std::move()
。不過此時還是會編譯失敗。錯誤信息如下:
Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/DemoTest.d" -MT"src/DemoTest.o" -o "src/DemoTest.o" "../src/DemoTest.cpp"
../src/DemoTest.cpp: In lambda function:
../src/DemoTest.cpp:31:18: error: binding reference of type ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}' to ‘std::remove_reference<const std::__cxx11::basic_string<char>&>::type {aka const std::__cxx11::basic_string<char>}' discards qualifiers
bar(std::move(str));
~~~~~~~~~^~~~~
../src/DemoTest.cpp:7:6: note: initializing argument 1 of ‘void bar(std::__cxx11::string&&)'
void bar(std::string &&str) {
^~~
make: *** [src/DemoTest.o] Error 1
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
在 funb 中, str 是個 const 對象,因而還是不行。
在函數 foo() 中定義的 func ,相對于 funa ,加了 mutable 修飾。此時還是會編譯失敗。錯誤信息如下:
Invoking: GCC C++ Compiler
g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"src/DemoTest.d" -MT"src/DemoTest.o" -o "src/DemoTest.o" "../src/DemoTest.cpp"
../src/DemoTest.cpp: In lambda function:
../src/DemoTest.cpp:37:12: error: cannot bind rvalue reference of type ‘std::__cxx11::string&& {aka std::__cxx11::basic_string<char>&&}' to lvalue of type ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}'
bar(str);
^
../src/DemoTest.cpp:7:6: note: initializing argument 1 of ‘void bar(std::__cxx11::string&&)'
void bar(std::string &&str) {
^~~
make: *** [src/DemoTest.o] Error 1
src/subdir.mk:18: recipe for target 'src/DemoTest.o' failed
無法將左值綁定到一個右值引用上。
在函數 foo() 中定義的 fund ,相對于 func ,在調用 bar() 時,為 str 裹上了 std::move() 。此時終于可以編譯成功,可以 move const 的 str 。
在函數 foo() 中定義的 fune ,相對于 funb ,以引用的方式捕獲了右值引用。在 fune 中調用 bar() ,就如同 foo() 直接調用 bar() 一樣。
在函數 foo() 中調用接收一個右值引用作為參數的函數 bar_bar() 生成一個函數。在函數 bar_bar() 中用 lambda 定義的函數對象 funf ,以引用的方式捕獲一個右值,并在 lambda 中訪問改對象。該 lambda 作為 bar_bar() 函數生成的函數對象。 foo() 中調用 bar_bar() 時傳入函數棧上定義的臨時對象 stra ,并將 bar_bar() 返回的函數對象作為返回值返回。在 main() 函數中用 funcg 接收 foo() 函數返回的函數對象,并調用 funcg ,此時會發生 crash 或能看到亂碼。crash 或亂碼是因為,在 funf 中,訪問的 str 對象實際上是 foo() 函數中定義的棧上臨時對象 stra , foo() 函數調用結束之后,棧上的臨時對象被釋放, main() 函數中調用 funcg 實際在訪問一個無效的對象,因而出現問題。
到此這篇關于C++ lambda 捕獲模式與右值引用的使用的文章就介紹到這了,更多相關C++ lambda 捕獲模式與右值引用內容請搜索億速云以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持億速云!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。