C++11: 右值引用

C++11: rvalue 右值

rvalue & lvalue(右值與左值)

左值與右值的定義是比較復(fù)雜蚪腋,下邊僅給出一個(gè)相對(duì)簡(jiǎn)單的定義,也是大家相對(duì)容易接受的一個(gè)定義吻商。

左值:能在內(nèi)存中取到地址的對(duì)象畔咧。
右值:不是左值的對(duì)象某抓。

左值舉例說(shuō)明一下:

int i;        // i 是一個(gè)左值
int* p = &i;  //獲取i的地址,且它的地址是唯一的
i = 2;        //因?yàn)榭梢垣@取到它的地址疮蹦,所以可以進(jìn)行賦值操作

class Dog;    //這是一個(gè)類(lèi)類(lèi)型  
dog d1;       //聲明了一個(gè)左值

在C++程序中诸迟,大部分的對(duì)象都是左值。

下面是右值的一些例子:

int x = 2;                // 2 是右值
int x = i+2;              //(i+2) 的結(jié)果是一個(gè)臨時(shí)變量,也是一個(gè)右值

int* l = &(i+2);          //錯(cuò)誤 ??
i+2 = 4;                  //錯(cuò)誤 ??
2 = i;                    //錯(cuò)誤 ??

dog d1;
d1 = Dog();              //Dog()會(huì)產(chǎn)生一個(gè)臨時(shí)變量阵苇,所以它也是一個(gè)右值

int sum (int x, int y)   
{
    return x+y;
}

int i = sum(3, 4);       //sum(3,4) 是一個(gè)右值

接下來(lái)我們來(lái)看一下左值引用和右值引用
先來(lái)看一下左值引用:

int i;
int& r = i;             // r 是對(duì)左值 i 的引用壁公,這是對(duì)左值的引用
int& r = 5;             //錯(cuò)誤 ??,無(wú)法對(duì)一個(gè)右值進(jìn)行左值引用操作绅项,但是有一種意外情況
const int& r = 5;       //常量左值引用可以引用右值


int square(int& x)
{
    return x * x;
}

square(i);              //正確 ??
square(40);             //錯(cuò)誤 ?? 

//如果希望square(40)正確調(diào)用的話(huà)紊册,可以使用上文的例外
int square(const int& x)
{
    return x * x;
}
square(40)              //正確 ??

接下來(lái)是右值引用,這里只是一個(gè)簡(jiǎn)單的語(yǔ)法上的例子快耿,下面有更詳細(xì)的例子

int a = 2;
int&& b = a + 2;        //b 擴(kuò)展了臨時(shí)變量 a + 2 的生命周期
int&& c = 5;

左值可以用來(lái)構(gòu)建右值

int i = 1;
int x = i + 2;          //i 是左值囊陡, i+2是右值

右值可以用來(lái)構(gòu)建左值

int v[3];               //3 是右值,v是左值
*(ptr+2) = 4;           //4與2 是右值掀亥,ptr是左值

三個(gè)常見(jiàn)的誤解:

  • 誤解一:函數(shù)和操作符的返回值為右值撞反。
    之所以這么認(rèn)為是因?yàn)椋瘮?shù)和操作符經(jīng)常會(huì)返回一些臨時(shí)變量搪花,而臨時(shí)變量是右值
int x = i + 3;          //i+3 返回了一個(gè)右值遏片,然后這個(gè)右值被賦值給了一個(gè)左值
int y = sum(3,4);       //sum(3,4) 也返回了一個(gè)右值


int myglobal;
int& foo()
{
    return myglobal;    //該函數(shù)返回了一個(gè)左值引用
}

foo() = 50;

array[3] = 50;          //[]操作符可以用來(lái)給元素賦值,其返回值也是左值引用
  • 誤解二:左值是可以被修改的撮竿。
    一個(gè)反例就是在左值前面加上const修飾
const int c = 1;
c = 2;
  • 誤解三:右值是不能被修改的吮便。
i+3 = 6;                //右值無(wú)法被修改
sum(3,4) = 7;           //右值無(wú)法被修改

class Dog
{   
public:
    Dog(){ state = 0;}
    void sleep(){state = 2;}
private:
    int state;
};
 
int main()
{
    Dog().sleep();      //Dog() 是一個(gè)右值,但是sleep會(huì)改變它的成員變量的值
    return 0;
}

每一個(gè)C++表達(dá)式幢踏,或者是一個(gè)左值髓需,或者是一個(gè)右值。如果一個(gè)對(duì)象有明確的可分辨的地址的話(huà)惑折,那它就是一個(gè)左值授账,反之是一個(gè)右值。

移動(dòng)語(yǔ)義

首先我們來(lái)看一下四個(gè)相互重載的函數(shù)惨驶,也就是白热,函數(shù)為左值引用或者右值引用是可以重載的。

// 1
void printInt(int& i) {std::cout<<"lvalue reference : "<<i<<endl;}
// 2
void printInt(int&& i) {std::cout<<"rvalue reference : "<<i<<endl;}
// 3
void printInt(const int& i) {std::cout<<"lvalue reference : "<<i<<endl;}
// 4
void printInt(int i) {std::cout<<"lvalue reference : "<<i<<endl;}

int main()
{
    int a = 5;          
    printInt(a);        //調(diào)用 1
    printInt(6);        //發(fā)生沖突粗卜,理論上2屋确、3、4都可以
    return 0;
}
class boVector
{
private:
    int size;
    double* arr_;       //會(huì)是一個(gè)特別大的數(shù)組

public:
    boVector(const boVector& rhs) //深拷貝
    {
        size = rhs.size;
        arr_ = new double[size];
        for(int i = 0; i<size; i++){ arr_[i] = rhs.arr_[i];}
    }

    boVector(const boVector&& rhs)  //淺拷貝
    {
        size = rhs.size;
        arr_ =rhs.arr_];
        rhs.arr_=nullptr;
    }

    ~boVector(){delete arr_;}
};

void foo(boVector v);

boVector createBoVector();

int main()
{
    boVector reuseable = createBoVector();

    //這里會(huì)調(diào)用拷貝構(gòu)造函數(shù)续扔,會(huì)構(gòu)造這個(gè)比較大的數(shù)組數(shù)據(jù)成員攻臀,
    //這將會(huì)是一個(gè)比較話(huà)費(fèi)時(shí)間的操作
    foo(resuable);      

    //createBoVector() 會(huì)返回一個(gè)臨時(shí)變量作為參數(shù)傳遞給foo,
    //這里會(huì)調(diào)用移動(dòng)拷貝構(gòu)造函數(shù)纱昧,不會(huì)重新創(chuàng)建比較大的數(shù)組
    foo(createBoVector());

    //如果resuable不再使用刨啸,我們也可以這么調(diào)用
    foo(std::move(reusable)); 
    //此時(shí) reusable.arr_ = nullptr
    return 0;
}

Note:
(1) 右值引用最有用的地方是給類(lèi)添加移動(dòng)拷貝構(gòu)造函數(shù)和移動(dòng)賦值操作符函數(shù)。
(2) C++11的STL容易都已經(jīng)實(shí)現(xiàn)了移動(dòng)語(yǔ)義识脆,理論上设联,只要你在編譯代碼的時(shí)候添加-std=c++11選項(xiàng)善已,就能獲得性能上的提升。

完美轉(zhuǎn)發(fā) prefect forwarding

template<typename T>
void relay(T arg)
{
    foo(T);
}

int main()
{
    boVector reusable = createBoVector();
    relay(reusable);

    relay(createBoVector());
}

這就做參數(shù)轉(zhuǎn)發(fā)

理想的情況下离例,relay的參數(shù)怎么轉(zhuǎn)發(fā)給foo函數(shù)呢换团,應(yīng)該要滿(mǎn)足兩個(gè)條件:

  1. 沒(méi)有花費(fèi)較大或者不必要的拷貝構(gòu)造函數(shù)的調(diào)用
  2. 左值需要被轉(zhuǎn)發(fā)為左值,右值需要被轉(zhuǎn)發(fā)為右值

滿(mǎn)足上述兩點(diǎn)稱(chēng)之為完美轉(zhuǎn)發(fā)
標(biāo)準(zhǔn)庫(kù)已經(jīng)幫我們提供了一個(gè)函數(shù)std::forward來(lái)進(jìn)行完美轉(zhuǎn)發(fā)宫蛆,該函數(shù)位于<utility>中艘包。利用該函數(shù)將relay改為

template<typename T>
void relay(T&& arg)
{
    foo(std::forward<T>(arg));
}

下面,我們?cè)谘a(bǔ)充一下完美轉(zhuǎn)發(fā)的一些細(xì)節(jié)耀盗。

首先來(lái)講一個(gè)概念想虎,引用坍縮(reference collapse),我覺(jué)得中文的翻譯怪怪的袍冷,還是英文比較好理解磷醋,其實(shí)就是有多個(gè)&的時(shí)候我們?cè)趺刺幚怼?/p>

typedef int&  lref;
typedef int&& rref;
int n;
lref&  r1 = n; // r1 的類(lèi)型是 int&
lref&& r2 = n; // r2 的類(lèi)型是 int&
rref&  r3 = n; // r3 的類(lèi)型是 int&
rref&& r4 = 1; // r4 的類(lèi)型是 int&&

根據(jù)引用坍縮,我們可以得到

//T&& 被一個(gè)右值初始化
relay(9); => T = int&& => T&& int&& && = int&&

//T&& 被一個(gè)左值初始化
relay(x); => int& => T&& = int& && = int&

這里的T&&不再是右值引用胡诗,而是universal reference
T&&Universial Reference的條件:

  1. T 是一個(gè)模板類(lèi)型邓线。
  2. 類(lèi)型推倒是施加于T的,T是一個(gè)函數(shù)模板參數(shù)煌恢,而不是類(lèi)模板參數(shù)骇陈。

然后標(biāo)注庫(kù)還提供了一個(gè)類(lèi)型工具remove_reference

template<class T>
struct remove_reference;

remove_reference<int&>::type i; // int i

remove_reference<int>::type i;  // int i
template < typename T >
void relay(T&& arg)
{
    foo(std::forward<T>(arg));
}

template < class T >
T&& forward(typename remove_reference<T>::type& arg)
{
    return static_cast<T&&>(arg);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瑰抵,隨后出現(xiàn)的幾起案子你雌,更是在濱河造成了極大的恐慌,老刑警劉巖二汛,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婿崭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肴颊,警方通過(guò)查閱死者的電腦和手機(jī)氓栈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)婿着,“玉大人授瘦,你說(shuō)我怎么就攤上這事【顾危” “怎么了提完?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)丘侠。 經(jīng)常有香客問(wèn)我徒欣,道長(zhǎng),這世上最難降的妖魔是什么蜗字? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任帚称,我火速辦了婚禮官研,結(jié)果婚禮上秽澳,老公的妹妹穿的比我還像新娘闯睹。我一直安慰自己,他們只是感情好担神,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布楼吃。 她就那樣靜靜地躺著,像睡著了一般妄讯。 火紅的嫁衣襯著肌膚如雪孩锡。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天亥贸,我揣著相機(jī)與錄音躬窜,去河邊找鬼。 笑死炕置,一個(gè)胖子當(dāng)著我的面吹牛荣挨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播朴摊,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼默垄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了甚纲?” 一聲冷哼從身側(cè)響起口锭,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎介杆,沒(méi)想到半個(gè)月后鹃操,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡春哨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年荆隘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悲靴。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡臭胜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出癞尚,到底是詐尸還是另有隱情耸三,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布浇揩,位于F島的核電站仪壮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏胳徽。R本人自食惡果不足惜积锅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一爽彤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缚陷,春花似錦适篙、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至虎锚,卻和暖如春硫痰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窜护。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工效斑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柱徙。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓缓屠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親坐搔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子藏研,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容