1. 程式人生 > >深入淺出C++11(3) -- 右值引用和move語義

深入淺出C++11(3) -- 右值引用和move語義

右值引用

什麼是lvalue, 什麼是rvalue?

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

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

也可以這樣理解:

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

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

例子:

int a = 10;
int b = 20;
int *pFlag = &a;
vector<int> 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分別是左值還是右值?、

  • a和b都是持久物件(可以對其取地址),是左值;
  • a+b是臨時物件(不可以對其取地址),是右值;
  • a++是先取出持久物件a的一份拷貝,再使持久物件a的值加1,最後返回那份拷貝,而那份拷貝是臨時物件(不可以對其取地址),故其是右值;
  • ++a則是使持久物件a的值加1,並返回那個持久物件a本身(可以對其取地址),故其是左值;
  • pFlag和*pFlag都是持久物件(可以對其取地址),是左值;
  • vctTemp[0]呼叫了過載的[]操作符,而[]操作符返回的是一個int &,為持久物件(可以對其取地址),是左值;
  • 100和string("hello")是臨時物件(不可以對其取地址),是右值;
  • str1是持久物件(可以對其取地址),是左值;
  • str1+str2是呼叫了+操作符,而+操作符返回的是一個string(不可以對其取地址),故其為右值;
  •  m是一個常量引用,引用到一個右值,但引用本身是一個持久物件(可以對其取地址),為左值。
    //先來看一個例子:

    //stack 分配4位元組,並設定值5.
    int a = 5;

變數a的儲存空間是 &a (a的lvalue), 儲存空間&a上的值為5(a的rvalue).

再看一個例子:

    int* p = NULL;
    p = new int(5);


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

對於p:  lvalue為&p(p的地址), rvalue為&p地址上的值

對於*p: lvalue為p(*p的地址),lvalue為p地址上的值

左值引用

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

int a = 5;
int& v1 = a;
const int& v2 = 5;

非常量左值引用

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

    int a = 5;
    const int b = 5;

    int& v1 = a; // 繫結到非常量左值
    int& v2 = 5; // 常量右值, compile error
    int& v3 = b; // 常量左值, compile error
    int& v4 = a + b; // 非常量右值, compile error, a + b為臨時物件

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

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

常量左值引用

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

    int a = 5;
    const int b = 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 語義。


右值引用

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

非常量右值引用

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

    int a = 5;
    const int b = 5;

    int&& v1 = 5;
    int&& v2 = a;   //compile error
    int&& v3 = b;   //compile error
    int&& v4 = a + b;

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

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

 常量右值引用

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

    int a = 5;
    const int b = 5;

    const int&& v1 = 5;
    const int&& v2 = a; //compile error
    const int&& v3 = b; //compile error
    const int&& v4 = a + b;

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

bool is_r_value(int &&) { return true; }
bool is_r_value(const int &) { return false; }

void test(int && i)
{
    bool isRValue = is_r_value(i); // i 為具名變數,即使被宣告成右值也不會被認定是右值。false
    isRValue = is_r_value(std::move<int&>(i)); // 使用 std::move<T>() 取得右值。 true
}

move語義

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

vector<int> getVector()
{
 vector<int> v;
 v.push_back(1);
 v.push_back(2);
 v.push_back(3);
 v.push_back(4);
 return v;
}
當使用vector<int> v = getVector()進行初始化時,實際上呼叫了三次建構函式。

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

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

 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<T>&&。如果vector沒有 move 建構函式,那麼複製建構函式將被呼叫,以const std::vector<T> &的正常形式。 如果它確實有 move 建構函式,那麼就會呼叫 move 建構函式,這能夠免除大幅的記憶體配置。


相關推薦

深入淺出C++113 -- 引用move語義

右值引用 什麼是lvalue, 什麼是rvalue? lvalue: 具有儲存性質的物件,即lvalue物件,是指要實際佔用記憶體空間、有記憶體地址的那些實體物件,例如:變數(variables)、函式、函式指標等。 rvalue:相比較於lvalue就是所謂的沒有儲存性質

C++11 標準新特性: 引用與轉移語義

https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/ 特性的目的 右值引用 (Rvalue Referene) 是 C++ 新標準 (C++11, 11 代表 2011 年 ) 中引入的新特性

C++11 新特性之引用轉移建構函式

問題背景 #include <iostream> usingnamespace std;   vector<int> doubleValues (const vector<int>& v)   {  

C++11嚐鮮:引用轉發型引用

右值引用 為了解決移動語義及完美轉發問題,C++11標準引入了右值引用(rvalue reference)這一重要的新概念。右值引用採用T&&這一語法形式,比傳統的引用T&(如今被稱作左值引用 lvalue reference)多一個&。

C++的雜七雜八:我家的返回才不可能這麼傲嬌引用移動語義

大凡程式語言,都會有“函式”這個概念。而對於外部而言,一個函式最重要的部分就是它的返回值了。 說這裡,返回值其實應該是一個很簡單的話題。當需要通過函式傳遞一個值出去的時候,使用返回值不是理所當然的嘛,比如說,像下面這樣: int add(int a, int b)

C++11新特性之引用

什麼是左值,什麼是右值? An l-value expression refers to an object's identity, whereas a r-value expression refers to an object's value. 左值標識物件的身份,右值標識物件的值(我把它理解為被物

[轉] c++11 int&& 引用

[轉自 http://blog.csdn.net/yuanwang1986/article/details/8666459 ] C++ 11中引入的右值引用正好可用於標識一個非常量右值。C++ 11中用&表示左值引用,用&&表示右值引用,如: int &&a = 1

C++ 面試 C++ 11 新特性之引用與移動

右值引用 什麼是左值,什麼是右值,簡單說左值可以賦值,右值不可以賦值。以下面程式碼為例,“ A a = getA();”該語句中a是左值,getA()的返回值是右值。 #include <iostream> class A { public

C++11:深入理解引用move語義完美轉發

深入右值引用,move語義和完美轉發 轉載請註明:http://blog.csdn.net/booirror/article/details/45057689 乍看起來,move語義使得你可以用廉價的move賦值替代昂貴的copy賦值,完美轉發使得你可以將傳來的任意

C++引用移動語義淺說

1、神馬叫右值和右值引用C++中所有的值分兩種,一種叫左值(可以取地址,有名字的),一種叫右值(不可以取地址,沒有名字的)。常見的如 int a  = b+c ;表示式中a  可以取地址為左值,(b+c

引用move語義

右值 C語言中,左值(left value, lvalue)只出現在賦值符左邊的量,右值(right value, rvalue)是出現在賦值符右邊的量。在C++中,右值的定義稍微不同,每一個表示式都會產生一個左值或者右值,所以表示式也稱左值表示式或右值表示式

深入引用move語義完美轉發

轉載:http://blog.csdn.net/booirror/article/details/45057689 乍看起來,move語義使得你可以用廉價的move賦值替代昂貴的copy賦值,完美轉發使得你可以將傳來的任意引數轉發給 其他函式,而右值引用使得move語

3左右再探與decltype

left 哪些 而是 ++ 但是 是什麽 了解 ati pure Decltype 類型指示符 “引用從來都作為其所指對象的同義詞出現,只有用在decltype處是一個例外” 理解: Decltype和auto區別: 1. auto是從表達式類型推斷出要定義的變量類

C#基礎3

之間 sys delete cnblogs show tle 前臺 nco click 摘要: 基於.Net Framework的winform開發,主要是關於為winform平臺的一些控件的屬性以及事件的使用。 涉及內容: 1、Directory類 2

C# 抽象3

圖片 AD set 類繼承 oid C# pub AC color 接上章: 抽象類中有抽象方法,那麽可不可以有非抽象方法呢? 答案是可以的。 abstract class Human { public abstract voi

初學C語言3

1、求一個數字的二進位制數中1的個數 2 0000 0010 1 2、給定一個數字,求這個數字是幾位數? 順序列印每一位數字 逆序列印每一位數字 12345 5 1 2 3 4 5 5 4 3 2 1 3、求斐波那契數列的第40項的和為多少? 1 1 2 3 5 8 13 4、輸出 100-

SlwUiSuper Lightweight UI前端js外掛菜單系列3鍵選單SlwCtxMenu,導航選單SlwMenu

      今天介紹前端js外掛菜單系列(3)右鍵選單SlwCtxMenu,導航選單SlwMenu。 基於jQuery,瀏覽器相容(Browser Support) Internet Explore

c基礎3

1.逗號表示式 int a,b; int c = (a=20,b=30);//這裡的c的值為30,逗號表示式會選取最後一個表示式的值作為它的值。 又如 int i; for(i=0,printf(" ");i<10,printf(" ");printf

Why Java Sucks and C# Rocks3:Attribute與Annotation

上一篇文章裡我談了Java和C#語言中對於基礎型別的不同態度,我認為C#把基礎型別視做物件的做法比Java更有“萬物皆物件”的理念,使用起來也更為方便。此外,C#擁有一個Java 1.4所不存在的特性,即Attribute(自定義特性),而在之後的Java 5.0中也增加了類似的功能,這便是Annotatio

《深入理解C++11》筆記–引用:移動語義完美轉發

上一篇:《深入理解C++11》筆記–建構函式 這篇文章介紹的了第三章中右值引用相關的內容。在介紹該內容之前,會對一些相關問題進行解釋,便於理解後面的內容。 並且,提前說明,許多編譯器會多拷貝構造和移動構造進行優化省略,這樣就看不到拷貝構造和移動構造的過程,需