溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++ lambda 捕獲模式與右值引用的使用

發(fā)布時間:2020-09-12 13:10:02 來源:腳本之家 閱讀:602 作者:WolfcsTech 欄目:編程語言

lambda 表達(dá)式和右值引用是 C++11 的兩個非常有用的特性。

lambda 表達(dá)式實際上會由編譯器創(chuàng)建一個 std::function 對象,以值的方式捕獲的變量則會由編譯器復(fù)制一份,在 std::function 對象中創(chuàng)建一個對應(yīng)的類型相同的 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

由上面調(diào)用 funca 時的輸出,可以看到 lambda 表達(dá)式以值的方式捕獲的對象 str,其地址在 lambda 表達(dá)式內(nèi)部和外部是不同的。

std::function 類對象和普通的魔板類對象一樣,可以拷貝構(gòu)造,如:

std::function<void()> funcb = funca;

由調(diào)用 funcb 時的輸出,可以看到拷貝構(gòu)造時是做了逐成員的拷貝構(gòu)造。

std::function 類對象可以賦值,如:

std::function<void()> funcc;
funcc = funca;

由調(diào)用 funcc 時的輸出,可以看到賦值時是做了逐成員的賦值。

std::function 類對象可以移動構(gòu)造,如:

std::function<void()> funcd = std::move(funca);

由移動構(gòu)造之后,調(diào)用 funca 和 funcd 時的輸出,可以看到移動構(gòu)造時是做了逐成員的移動構(gòu)造。

std::function 類對象可以移動賦值,如:

 

 std::function<void()> funce;
 funce = std::move(funcb);

 printf("funcb\n");
// funcb();

這里把移動賦值之后對 funcb 的調(diào)用注釋掉了,這是因為,作為源的 funcb 在移動賦值之后被調(diào)用是,會拋出異常,如:

String address 0x562334c34280 (main lambda), str test
funcb
terminate called after throwing aninstanceof 'std::bad_function_call'
 what(): bad_function_call

同時,由調(diào)用 funce 時的輸出可以看到,該輸出與 funcb 在移動賦值之前被調(diào)用時的輸出完全相同。 即移動賦值是將對象整體 move 走了,這與移動構(gòu)造時的行為不太一樣。

std::function 類對象的拷貝構(gòu)造或者賦值,也需要滿足類型匹配原則,如:

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;
}

上面這段代碼在執(zhí)行時,輸出如下:

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 函數(shù)接收右值引用作為參數(shù),由 funca 函數(shù)內(nèi)部及函數(shù)調(diào)用前后的輸出可以看到, std::move() 本身什么都沒做,單單調(diào)用 std::move() 并不會將原來的對象的內(nèi)容移動到任何地方。 std::move() 只是一個簡單的強(qiáng)制類型轉(zhuǎn)換,將左值轉(zhuǎn)為右值引用。同時可以看到,用右值引用作為參數(shù)構(gòu)造對象,也并沒有對右值引用所引用的對象產(chǎn)生任何影響。

funcb 函數(shù)接收左值引用作為參數(shù),上面的代碼中,如下這一行注釋掉了:

// funcb(std::move(str));

這是因為, funcb 不能用一個右值引用作為參數(shù)來調(diào)用。用右值引用作為參數(shù),調(diào)用接收左值引用作為參數(shù)的函數(shù) 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 左值引用作為參數(shù),如 void funcb(const std::string &str) ,則在調(diào)用該函數(shù)時,可以用右值引用作為參數(shù),此時 funcb 的行為與 funca 基本相同。

funcc 函數(shù)接收左值作為參數(shù),由 funcc 函數(shù)內(nèi)部及函數(shù)調(diào)用前后的輸出可以看到,由于有了左值作為接收者,傳入的右值引用所引用的對象的值被 move 走,進(jìn)入函數(shù)的參數(shù)棧對象中了。

funcd 函數(shù)與 funca 函數(shù)一樣,接收右值引用作為參數(shù),但 funcd 的特別之處在于,在函數(shù)內(nèi)部,右值構(gòu)造了一個新的對象,因而右值引用原來引用的對象的值被 move 走,進(jìn)入了新構(gòu)造的對象中。

再來看一段示例代碼:

#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����

在函數(shù) foo() 中定義的 funa 及對 funa 的調(diào)用被注釋掉了,這是因為這段代碼會導(dǎo)致編譯失敗,具體的錯誤信息如下:

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 表達(dá)式中,以值的方式捕獲右值引用時,會在編譯器為該 lambda 表達(dá)式生成的 std::function 類中生成一個 const 對象,const 對象是不能作為右值引用來調(diào)用接收右值引用為參數(shù)的函數(shù)的。

在函數(shù) foo() 中定義的 funb ,相對于 funa ,在調(diào)用 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 對象,因而還是不行。

在函數(shù) 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

無法將左值綁定到一個右值引用上。

在函數(shù) foo() 中定義的 fund ,相對于 func ,在調(diào)用 bar() 時,為 str 裹上了 std::move() 。此時終于可以編譯成功,可以 move const 的 str 。

在函數(shù) foo() 中定義的 fune ,相對于 funb ,以引用的方式捕獲了右值引用。在 fune 中調(diào)用 bar() ,就如同 foo() 直接調(diào)用 bar() 一樣。

在函數(shù) foo() 中調(diào)用接收一個右值引用作為參數(shù)的函數(shù) bar_bar() 生成一個函數(shù)。在函數(shù) bar_bar() 中用 lambda 定義的函數(shù)對象 funf ,以引用的方式捕獲一個右值,并在 lambda 中訪問改對象。該 lambda 作為 bar_bar() 函數(shù)生成的函數(shù)對象。 foo() 中調(diào)用 bar_bar() 時傳入函數(shù)棧上定義的臨時對象 stra ,并將 bar_bar() 返回的函數(shù)對象作為返回值返回。在 main() 函數(shù)中用 funcg 接收 foo() 函數(shù)返回的函數(shù)對象,并調(diào)用 funcg ,此時會發(fā)生 crash 或能看到亂碼。crash 或亂碼是因為,在 funf 中,訪問的 str 對象實際上是 foo() 函數(shù)中定義的棧上臨時對象 stra , foo() 函數(shù)調(diào)用結(jié)束之后,棧上的臨時對象被釋放, main() 函數(shù)中調(diào)用 funcg 實際在訪問一個無效的對象,因而出現(xiàn)問題。

到此這篇關(guān)于C++ lambda 捕獲模式與右值引用的使用的文章就介紹到這了,更多相關(guān)C++ lambda 捕獲模式與右值引用內(nèi)容請搜索億速云以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持億速云!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI