1.?左值、右值、左值引用以及右值引用
- 左值:一般指的是在內(nèi)存中有對應(yīng)的存儲(chǔ)單元的值,最常見的就是程序中創(chuàng)建的變量
- 右值:和左值相反,一般指的是沒有對應(yīng)存儲(chǔ)單元的值(寄存器中的立即數(shù),中間結(jié)果等),例如一個(gè)常量,或者表達(dá)式計(jì)算的臨時(shí)變量
int x = 10 int y = 20 int z = x + y //x, y , z 是左值 //10 , 20,x + y 是右值,因?yàn)樗鼈冊谕瓿少x值操作后即消失,沒有占用任何資源
- 左值引用:C++中采用 &對變量進(jìn)行引用,這種常規(guī)的引用就是左值引用
- 右值引用:右值引用最大的作用就是讓一個(gè)左值達(dá)到類似右值的效果(下面程序舉例),讓變量之間的轉(zhuǎn)移更符合“語義上的轉(zhuǎn)移”,以減少轉(zhuǎn)移之間多次拷貝的開銷。右值引用符號(hào)是&&。
例如,對于以下程序,我們要將字符串放到vector中,且我們后續(xù)的代碼中不再用到x:
std::vector<std::string> vec; std::string x = "abcd"; vec.push_back(x); std::cout<<"x: "<<x<<"\n"; std::cout<<"vector: "<< vec[0]<<"\n"; //-------------output------------------ // x: abcd // vector: abcd
?該程序在真正執(zhí)行的過程中,實(shí)際上是復(fù)制了一份字符串x,將其放在vector中,這其中多了一個(gè)拷貝的開銷和內(nèi)存上的開銷。但如果x以及沒有作用了,我們希望做到的是?真正的轉(zhuǎn)移,即x指向的字符串移動(dòng)到vector中,不需要額外的內(nèi)存開銷和拷貝開銷。因此我們希望讓變量 x傳入到push_back?表現(xiàn)的像一個(gè)右值?,這個(gè)時(shí)候就體現(xiàn)右值引用的作用,只需要將x
的右值引用傳入就可以。
改進(jìn)成如下代碼:
std::vector<std::string> vec; std::string x = "abcd"; vec.push_back(std::move(x)); <--------------- 使用了std::move,任何的左值/右值通過std::move都轉(zhuǎn)化為右值引用 std::cout<<"x: "<<x<<"\n"; std::cout<<"vector: "<< vec[0]<<"\n"; //-------------output------------------ // x: // vector: abcd
可以看到,完成`push_back`后x
是空的。
?
?2. 移動(dòng)語義
?移動(dòng)語義是通過移動(dòng)構(gòu)造和移動(dòng)賦值避免無意義的拷貝操作。
2.1 使用std::move實(shí)現(xiàn)移動(dòng)構(gòu)造
定義:采用右值引用作為參數(shù)的構(gòu)造函數(shù)又稱作移動(dòng)構(gòu)造函數(shù)。此時(shí)不需要額外的拷貝操作,也不需要新分配內(nèi)存。
使用場景:對于一個(gè)值(比如數(shù)組、字符串、對象等)如果在執(zhí)行某個(gè)操作后不再使用,那么這個(gè)值就叫做將亡值(Expiring Value),因此對于本次操作我們就沒必要對該值進(jìn)行額外的拷貝操作,而是希望直接轉(zhuǎn)移,盡可能減少額外的拷貝開銷,操作后該值也不再占用額外的資源。
使用函數(shù):std::move,任何的左值/右值通過std::move都轉(zhuǎn)化為右值引用
看如下例子,
#include <iostream> #include <vector> #include <string> class A { public: A(){} A(size_t size): size(size), array((int*) malloc(size)) { std::cout << "create Array,memory at: " << array << std::endl; } ~A() { free(array); } A(A &&a) : array(a.array), size(a.size) { a.array = nullptr; std::cout << "Array moved, memory at: " << array << std::endl; } A(A &a) : size(a.size) { array = (int*) malloc(a.size); for(int i = 0;i < a.size;i++) array[i] = a.array[i]; std::cout << "Array copied, memory at: " << array << std::endl; } size_t size; int *array; }; int main() { std::vector<A> vec; A a = A(10); vec.push_back(a); return 0; } //----------------output-------------------- // create Array,memory at: 0x600002a28030 // A a = A(10); 調(diào)用了 構(gòu)造函數(shù)A(size_t size){} // Array copied, memory at: 0x600002a28050 //vec push的時(shí)候拷貝一份,調(diào)用構(gòu)造函數(shù)A(A &a){}
從輸出可以看到,每次進(jìn)行push_back的時(shí)候,會(huì)重新創(chuàng)建一個(gè)對象,調(diào)用了左值引用A(A &a) : size(a.size)
對應(yīng)的構(gòu)造函數(shù),將對象中的數(shù)組重新深拷貝一份。
如果該用右值引用進(jìn)行優(yōu)化,如下
int main () { std::vector<A> vec; A a = A(10); vec.push_back(std::move(a)); return 0; } //----------------output-------------------- // create Array,memory at: 0x600003a84030 // Array moved, memory at: 0x600003a84030
可以看到,這個(gè)時(shí)候雖然也重新創(chuàng)建了一個(gè)對象,但是調(diào)用的是這個(gè)構(gòu)造函數(shù)A(A &&a) : array(a.array), size(a.size)
(這種采用右值引用作為參數(shù)的構(gòu)造函數(shù)又稱作移動(dòng)構(gòu)造函數(shù)),此時(shí)不需要額外的拷貝操作,也不需要新分配內(nèi)存。
?
3. 完美轉(zhuǎn)發(fā)
使用函數(shù):std::forward,如果傳遞的是左值轉(zhuǎn)發(fā)的就是左值引用,傳遞的是右值轉(zhuǎn)發(fā)的就是右值引用。
?
3.1 引用折疊
在具體介紹std::forward之前,需要先了解C++的引用折疊規(guī)則,對于一個(gè)值引用的引用最終都會(huì)被折疊成左值引用或者右值引用。
- T& & -> T& (對左值引用的左值引用是左值引用)
- T& && -> T& (對左值引用的右值引用是左值引用)
- T&& & ->T& (對右值引用的左值引用是左值引用)
- T&& && ->T&& (對右值引用的右值引用是右值引用)
總結(jié)一句話,只有對于右值引用的右值引用折疊完還是右值引用,其他都會(huì)被折疊成左值引用。
?
3.2 使用std::forward實(shí)現(xiàn)完美轉(zhuǎn)發(fā)
std::forward的作用就是完美轉(zhuǎn)發(fā),確保轉(zhuǎn)發(fā)過程中引用的類型不發(fā)生任何改變,左值引用轉(zhuǎn)發(fā)后一定還是左值引用,右值引用轉(zhuǎn)發(fā)后一定還是右值引用!
下面是一個(gè)使用 std::forward 的例子:
#include <iostream> #include <utility> void func(int& x) { std::cout << "lvalue reference: " << x << std::endl; } void func(int&& x) { std::cout << "rvalue reference: " << x << std::endl; } template<typename T> void wrapper(T&& arg) { func(std::forward<T>(arg)); } int main() { int x = 42; wrapper(x); // lvalue reference: 42 wrapper(1); // rvalue reference: 1 return 0; }
在上面的例子中,我們定義了兩個(gè)函數(shù) func,一個(gè)接受左值引用,另一個(gè)接受右值引用。然后我們定義了一個(gè)模板函數(shù) wrapper,在 wrapper 函數(shù)中,我們使用 std::forward 函數(shù)將參數(shù) arg 轉(zhuǎn)發(fā)給 func 函數(shù)。通過使用 std::forward,我們可以確保 func 函數(shù)接收到的參數(shù)的左右值特性與原始參數(shù)保持一致。
- 當(dāng)向wrapper里面?zhèn)魅離的時(shí)候,wrapper推導(dǎo)認(rèn)為 T是一個(gè)左值引用int &,通過引用折疊原則(看萬能引用文章)int && + & = int &,相當(dāng)于wrapper(int& arg),同時(shí)我們知道了T推導(dǎo)為int&,那么在向func傳遞的時(shí)候,就是func(std::forward<int&> (arg)) ,那么func會(huì)以左值引用的形式 func(int& x) 調(diào)用arg。
- 當(dāng)向wrapper里面?zhèn)魅?的時(shí)候,wrapper推導(dǎo)認(rèn)為T是一個(gè)右值引用int&& ,通過引用折疊原則,int && + && =int&& ,相當(dāng)于wrapper(int&& arg),同時(shí)我們知道了T推導(dǎo)為int&&,那么在向func傳遞的時(shí)候,就是func(std::forward<int&&>(arg)),那么func會(huì)以左值引用的形式func(int&& x)調(diào)用arg。
?
另一個(gè)例子:
class Test{}; void B(Test& a) {cout << "B&" << endl;} void B(Test&& a) {cout << "B&&" << endl;} template<typename T> void A(T &&a) { B(std::forward<T>(a)); } int main() { Test a; A(std::move(a)); A(a); return 0; } ////// //輸出結(jié)果 B&& B&
?
?
?
參考鏈接:https://zhuanlan.zhihu.com/p/469607144文章來源:http://www.zghlxwxcb.cn/news/detail-745905.html
https://www.jb51.net/article/278300.htm文章來源地址http://www.zghlxwxcb.cn/news/detail-745905.html
到了這里,關(guān)于c++右值引用、移動(dòng)語義、完美轉(zhuǎn)發(fā)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!