右值引用和move語義

2021-07-16 02:42:52 字數 4717 閱讀 3203

標籤: c++11

c++lvalue

rvalue

2013-10-04 20:49

2909人閱讀收藏 

舉報

c++(6)

目錄(?)

[+]

lvalue:具有儲存性質的物件,即lvalue物件,是指要實際占用記憶體空間、有記憶體位址的那些實體物件,例如:變數(variables)、函式、函式指標等。

rvalue:相比較於lvalue就是所謂的沒有儲存性質的物件, 也就是臨時物件。

也可以這樣理解: 

lvalue:通過它能夠找到記憶體中存放的變數(location value),位於賦值運算子左,可以賦值。

rvalue:存放在lvalue對應的記憶體中的東西(register value), 位於賦值運算子左,不可賦值。

對左值和右值的乙個最常見的誤解是:等號左邊的就是左值,等號右邊的就是右值。左值和右值都是針對表示式而言的,左值是指表示式結束後依然存在的持久物件,右值是指表示式結束時就不再存在的臨時物件

例子:[cpp]view plain

copy

inta = 10;  

intb = 20;  

int*pflag = &a;  

vector

> vcttemp;  

vcttemp.push_back(1);  

string str1 = "hello "

;  string str2 = "world"

;  const

int&m = 1;  

請問,a,b, a+b, a++, ++a, pflag, *pflag, vcttemp[0], 100, string("hello"), str1, str1+str2, m分別是左值還是右值?、

[cpp]view plain

copy

//先來看乙個例子:

//stack 分配4位元組,並設定值5.

inta = 5;  

變數a的儲存空間是 

&a (a的lvalue)

, 儲存空間&a上的值為

5(a的rvalue)

.再看乙個例子:

[cpp]view plain

copy

int* p = null;  

p = new

int(5);  

這裡分配了兩個記憶體, 乙個是在stack上(p, 位址為&p), 乙個是在heap上(*p, 位址為p)

左值引用根據其修飾符的不同,可以分為非常量左值引用和常量左值引用。

[cpp]view plain

copy

inta = 5;  

int& v1 = a;  

const

int& v2 = 5;  

只能繫結到非常量左值,不能繫結到常量左值、非常量右值和常量右值。

[cpp]view plain

copy

inta = 5;  

sp;   const

intb = 5;  

int& v1 = a; 

// 繫結到非常量左值

int& v2 = 5; 

// 常量右值, compile error

int& v3 = b; 

// 常量左值, compile error

int& v4 = a + b; 

// 非常量右值, compile error, a + b為臨時物件

如果允許繫結到常量左值和常量右值,則非常量左值引用可以用於修改常量左值和常量右值,這明顯違反了其常量的含義。

如果允許繫結到非常量右值,則會導致非常危險的情況出現,因為非常量右值是乙個臨時物件,非常量左值引用可能會使用乙個已經被銷毀了的臨時物件。

可以繫結到所有型別的值,包括非常量左值、常量左值、非常量右值和常量右值。

[cpp]view plain

copy

inta = 5;  

bsp;   const

intb = 5;  

const

int& v1 = a; 

// 繫結到非常量左值   

const

int& v2 = 5; 

// 常量右值

const

int& v3 = b; 

// 常量左值

const

int& v4 = a + b; 

// 非常量右值, a + b為臨時物件

可以看出,使用左值引用時,我們無法區分出繫結的是否是非常量右值的情況(無法區分上面的v2,v4)

c++11 增加乙個新的非常數引用(reference)型別,稱作右值引用(r-value reference),標記為t &&。右值引用所引用的臨時物件可以在該臨時物件被初始化之後做修改,這是為了允許 move 語義。

右值引用根據其修飾符的不同,也可以分為非常量右值引用和常量右值引用。

只能繫結到非常量右值,不能繫結到非常量左值、常量左值和常量右值。

[cpp]view plain

copy

inta = 5;  

const

intb = 5;  

int&& v1 = 5;  

int&& v2 = a;   

//compile error

int&& v3 = b;   

//compile error

int&& v4 = a + b;  

如果允許繫結到非常量左值,則可能會錯誤地竊取乙個持久物件的資料,而這是非常危險的;

如果允許繫結到常量左值和常量右值,則非常量右值引用可以用於修改常量左值和常量右值,這明顯違反了其常量的含義。

可以繫結到非常量右值和常量右值,不能繫結到非常量左值和常量左值

[cpp]view plain

copy

inta = 5;  

const

intb = 5;  

const

int&& v1 = 5;  

const

int&& v2 = a; 

//compile error

const

int&& v3 = b; 

//compile error

const

int&& v4 = a + b;  

注: 基於安全考慮,具有名字的宣告為右值的引數不會被認定為右值:

[cpp]view plain

copy

bool

is_r_value(

int&&)   

bool

is_r_value(

const

int&)   

void

test(

int&& i)    

那麼,為什麼要對非常量右值進行區分呢,區分出來了又有什麼好處呢?這就牽涉到c++中乙個著名的效能問題——拷貝臨時物件。考慮下面的**:

[cpp]view plain

copy

vector<

int> getvector()    

當使用vectorv = getvector()進行初始化時,實際上呼叫了三次建構函式。

儘管有些編譯器可以採用rvo(return value optimization)來進行優化,但優化工作只在某些特定條件下才能進行。可以看到,上面很普通的乙個函式呼叫,由於存在臨時物件的拷貝,導致了額外的兩次拷貝建構函式和析構函式的開銷。當然,我們也可以修改函式的形式為void getvector(vector&v),但這並不一定就是我們需要的形式

另外,考慮下面字串的連線操作:

[cpp]view plain

copy

string s1(

"hello"

);  

string s = s1 + "a"

+ "b"

+ "c"

+ "d"

+ "e"

;  

在對s進行初始化時,會產生大量的臨時物件,並涉及到大量字串的拷貝操作,這顯然會影響程式的效率和效能。怎麼解決這個問題呢?如果我們能確定某個值是乙個非常量右值(或者是乙個以後不會再使用的左值),則我們在進行臨時物件的拷貝時,可以不用拷貝實際的資料,而只是「竊取」指向實際資料的指標(類似於stl中的auto_ptr,會轉移所有權)。

在 c++11,乙個std::vector的 "move 建構函式" 對某個vector的右值引用可以單純地從右值複製其內部 c-style 陣列的指標到新的 vector,然後留下空的右值。這個操作不需要陣列的複製,而且空的臨時物件的析構也不會摧毀記憶體。傳回vector臨時物件的函式不需要顯式地傳回std::vector&&。如果vector沒有 move 建構函式,那麼複製建構函式將被呼叫,以const std::vector&的正常形式。 如果它確實有 move 建構函式,那麼就會呼叫 move 建構函式,這能夠免除大幅的記憶體配置。

C 11右值 右值引用以及move語義

1 字面常量 1 3,2 等 2 臨時物件 返回非引用型別的函式,算術 關係 位以及後置遞增 遞減原算符 注意 左值引用的函式,賦值 下標 解引用和前置遞增 遞減運算子返回都為左值 3 無名物件 4 一般函式的返回值也為右值 class myclass myclass 右值 1 右值引用只能繫結到臨...

C 11 右值引用與move語義

1.右值引用 1.1 右值 右值就是指在下乙個分號後 更準確的說是在包含右值的完整表示式的最後 銷毀的臨時物件。對於c 11,編譯器會依據引數是左值還是右值在複製建構函式和move建構函式間進行選擇。怎樣區分呢?the distinguishing criterion is if it has a ...

右值引用 move函式

為了支援移動操作,新標準引入了一種新的引用型別 右值引用 rvalue reference 所謂右值引用就是必須繫結到右值的引用。我們通過 而不是 來獲得右值引用,右值引用有乙個重要的性質 只能繫結到乙個將要銷毀的物件。因此,我們可以自由地將乙個右值引用的資源 移動 到另乙個物件中。一般而言,乙個左...