右值引用:移動語義和完美轉發(fā)

右值引用:移動語義和完美轉發(fā)

指針成員與拷貝構造

#include <iostream>
using namespace std;

class HasPtrMem {
public:
    HasPtrMem(): d(new int(0)) {}
    ~HasPtrMem() { delete d; }
    int * d;
};

int main() {
    HasPtrMem a;
    HasPtrMem b(a);
    cout << *a.d << endl;   // 0
    cout << *b.d << endl;   // 0
}   // 析構:運行時錯誤砾脑,多次在同一位置調用delete
  • 淺拷貝(shollow copy)

在未聲明拷貝構造函數(shù)時,編譯器也會為類生成一個淺拷貝構造函數(shù)辆憔。

解決淺拷貝問題的方法是用戶自定義拷貝函數(shù),進行“深拷貝”(deep copy)报嵌。

#include <iostream>
using namespace std;

class HasPtrMem {
public:
    HasPtrMem(): d(new int(0)) {}
    HasPtrMem(const HasPtrMem & h):
        d(new int(*h.d)) {} // 拷貝構造函數(shù),從堆中分配內(nèi)存熊榛,并用*h.d初始化
    ~HasPtrMem() { delete d; }
    int * d;
};

int main() {
    HasPtrMem a;
    HasPtrMem b(a);
    cout << *a.d << endl;   // 0
    cout << *b.d << endl;   // 0
}   // 正常析構析構

左值锚国,右值和右值引用

在C++11中所有的值必屬于左值(lvalue)、右值(rvalue)兩者之一玄坦,右值又可以細分為純右值(prvalue, Pure RValue)血筑、將亡值(xvalue, eXpiring Value)。

在C++11中可以取地址的煎楣、有名字的就是左值豺总,反之,不能取地址的择懂、沒有名字的就是右值(將亡值或純右值)喻喳。

a = b+c;

// a 左值, &a 合法
// b+c 是右值困曙, &(b+c) 非法

純右值

  • 非引用返回的函數(shù)返回的臨時變量值
  • 1+3 產(chǎn)生的臨時變量
  • 2, 'c', true
  • 類型轉換函數(shù)的返回值
  • lambda 表達式

將亡值

將亡值則是 C++11 新增的跟右值引用相關的表達式表伦,這樣表達式通常是將要被移動對象.

  • 返回右值引用 T&& 的函數(shù)返回值
  • std::move 的返回值
  • 轉換為 T&& 的類型轉換函數(shù)的返回值

右值引用就是對一個右值進行引用的類型.

通常情況下,右值不具有名字,我們只能通過引用的方式找到它.

T&& a = RetureRvalue();
/*
 * a 是右值引用
 * RetureRvalue 返回一個臨時變量
 * a 這個右值引用 引用 RetureRvalue 返回的臨時變量
*/

右值引用和左值引用都是屬于引用類型。無論是聲明一個左值引用還是右值引用慷丽,都必須立即進行初始化蹦哼。而其原因可以理解為是引用類型本身自己并不擁有所綁定對象的內(nèi)存,只是該對象的一個別名要糊。左值引用是具名變量值的別名纲熏,而右值引用則是不具名(匿名)變量的別名。

通常情況下,右值引用不能綁定到任何左值上.

int c;
int &&d = c; // error, 左值引用不能綁定到左值上

C++98 左值引用是否可以綁定到右值上?

T& e = RetureRvalue();          // error
const T& f = RetureRvalue();    // ok

在 C++98 中,常量左值引用就是個"萬能"的引用類型.可以接受非常量左值锄俄、常量左值局劲、右值對其進行初始化.而且使用右值對其初始化的時候,常量左值引用還可以向右值引用一樣將右值的生命周期延長.不過相比于右值引用所引用的右值,常量左值引用所引用的右值在它的"余生"中只能是只讀的.相對的,非常量左值引用只能接受非常量左值對其初始化.

在 C++98 中使用 常量左值引用 來減少臨時對象的開銷:

#include <iostream>
using namespace std;

struct Copyable {
    Copyable() {}
    Copyable(const Copyable &o) {
        cout << "Copied" << endl;
    }
};

Copyable ReturnRvalue() { return Copyable(); }
void AcceptVal(Copyable) {}
void AcceptRef(const Copyable & cp) {}//Copyable c = std::move(cp);}
void AcceptRRef(int && i) {i+=3; cout << (char)i << endl;}

int main() {
    cout << "Pass by value: " << endl;
    AcceptVal(ReturnRvalue()); // 臨時值被拷貝傳入
    cout << "Pass by reference: " << endl;
    AcceptRef(ReturnRvalue()); // 臨時值被作為引用傳遞
    AcceptRRef('c'); // 臨時值被作為引用傳遞
}

常量右值引用

const T&& crvalueref = RetureRvalue();

右值引用就是為了移動語義,而移動語義需要右值是可以被修改的奶赠,那么常量右值引用在移動語義中就沒有用處容握;二來如果要引用右值且讓右值不可以更改,常量左值引用就夠了车柠。

引用類型 非常量左值 常量左值 非常量右值 常量右值 注記
非常量左值引用 Y N N N
常量左值引用 Y Y Y Y 全部類型,可用于拷貝語義
非常量右值引用 N N Y N 用于移動語義剔氏、完美轉發(fā)
常量右值引用 N N Y Y 暫無用途
#include <type_traits>

std::cout << is_lvalue_reference<string &&>::value;
std::cout << is_rvalue_reference<string &&>::value;

移動語義

#include <iostream>
using namespace std;

class A {
public:
    int x;
    A(int x) : x(x)
    {
        cout << "Constructor" << endl;
    }
    A(A& a) : x(a.x)
    {
        cout << "Copy Constructor" << endl;
    }
    A& operator=(A& a)
    {
        x = a.x;
        cout << "Copy Assignment operator" << endl;
        return *this;
    }
    A(A&& a) : x(a.x)
    {
        cout << "Move Constructor" << endl;
    }
    A& operator=(A&& a)
    {
        x = a.x;
        cout << "Move Assignment operator" << endl;
        return *this;
    }
};

A GetA()
{
    return A(1);
}

A&& MoveA()
{
    return A(1);
}

int main()
{
    cout << "-------------------------1-------------------------" << endl;
    A a(1);
    cout << "-------------------------2-------------------------" << endl;
    A b = a;
    cout << "-------------------------3-------------------------" << endl;
    A c(a);
    cout << "-------------------------4-------------------------" << endl;
    b = a;
    cout << "-------------------------5-------------------------" << endl;
    A d = A(1);
    cout << "-------------------------6-------------------------" << endl;
    A e = std::move(a);
    cout << "-------------------------7-------------------------" << endl;
    A f = GetA();
    cout << "-------------------------8-------------------------" << endl;
    A&& g = MoveA();
    cout << "-------------------------9-------------------------" << endl;
    d = A(1);
}
/*
-------------------------1-------------------------
Constructor
-------------------------2-------------------------
Copy Constructor
-------------------------3-------------------------
Copy Constructor
-------------------------4-------------------------
Copy Assignment operator
-------------------------5-------------------------
Constructor
-------------------------6-------------------------
Move Constructor
-------------------------7-------------------------
Constructor
-------------------------8-------------------------
Constructor
-------------------------9-------------------------
Constructor
Move Assignment operator
*/
#include <iostream>
using namespace std;

class HasPtrMem {
public:
    HasPtrMem(): d(new int(0)) {
        cout << "Construct: " << ++n_cstr << endl;
    }
    HasPtrMem(const HasPtrMem & h): d(new int(*h.d)) {
        cout << "Copy construct: " << ++n_cptr << endl;
    }
    ~HasPtrMem() {
        delete d;
        cout << "Destruct: " << ++n_dstr << endl;
    }
    int * d;
    static int n_cstr;
    static int n_dstr;
    static int n_cptr;
};

int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_dstr = 0;
int HasPtrMem::n_cptr = 0;

HasPtrMem GetTemp() { return HasPtrMem(); }

int main() {
    HasPtrMem a = GetTemp();
}
/*
 * 程序輸出:
 * Construct: 1
 * Copy construct: 1
 * Destruct: 1
 * Copy construct: 2
 * Destruct: 2
 * Destruct: 3
*/
/*
 * 在新的版本的編譯器程序輸出:
 * Construct: 1
 * Destruct: 1
*/

本示例想說明一個問題塑猖,拷貝構造的調用,尤其是深拷貝構造的調用谈跛,會進行內(nèi)存的memcpy羊苟,消耗大量資源。

C++11 的移動語義(move semantics):

不會進行拷貝構造感憾,只是將臨時變量“偷來”蜡励。

#include <iostream>
using namespace std;

class HasPtrMem {
public:
    HasPtrMem(): d(new int(3)) {
        cout << "Construct: " << ++n_cstr << endl;
    }
    HasPtrMem(const HasPtrMem& h) : d(new int(*h.d)) {
        cout << "Copy construct: " << ++n_cptr << endl;
    }
    HasPtrMem(HasPtrMem && h): d(h.d) { // 移動構造函數(shù)
        h.d = nullptr;                  // 將臨時值的指針成員置空
        cout << "Move construct: " << ++n_mvtr << endl;
    }
    ~HasPtrMem() {
        delete d;
        cout << "Destruct: " << ++n_dstr << endl;
    }
    int * d;
    static int n_cstr;
    static int n_dstr;
    static int n_cptr;
    static int n_mvtr;
};

int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_dstr = 0;
int HasPtrMem::n_cptr = 0;
int HasPtrMem::n_mvtr = 0;

const HasPtrMem GetTemp() {
    HasPtrMem h;
    cout << "Resource from " <<  __func__ << ": " << hex << h.d << endl;
    return h;
}

int main() {
    const HasPtrMem && a = GetTemp();
    cout << "Resource from " <<  __func__ << ": " << hex << a.d << endl;
    // hex << a.d ===> hex(a.d)
}
/*
    Construct: 1
    Resource from GetTemp: 0x907ab0
    Resource from main: 0x907ab0
    Destruct: 1
*/
// 移動構造并沒有被調用,該示例無法說明任何問題.

std::move 強制轉換為右值

std::move 將一個左值強制轉換為右值引用阻桅,繼而我們通過右值使用該值凉倚,以用于移動語義。

// 等價于:
static_cast<T&&>(lvalue);

被強制轉化的左值嫂沉,其生命周期并沒有隨著左右值的變化而改變稽寒。

#include <iostream>

class Moveable {
public:
    Moveable(): i (new int(3)) {
        std::cout << "Moveable" << std::endl;
    }
    ~Moveable() { delete i; }
    Moveable(const Moveable & m) : i(new int(*m.i)) {}
    Moveable(Moveable && m) : i (m.i) {
        m.i = nullptr;
    }

    int *i;
};

int main()
{
    Moveable a;
    Moveable c(std::move(a));   // a 為左值,強制轉換為右值
    std::cout << *(a.i) << std::endl; // 在 std::move(a) 時趟章, a.i 就被設置為了 nullptr杏糙, 故這里運行時錯誤

    return 0;
}
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蚓土,隨后出現(xiàn)的幾起案子宏侍,更是在濱河造成了極大的恐慌,老刑警劉巖蜀漆,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谅河,死亡現(xiàn)場離奇詭異,居然都是意外死亡确丢,警方通過查閱死者的電腦和手機旧蛾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蠕嫁,“玉大人锨天,你說我怎么就攤上這事√甓荆” “怎么了病袄?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赘阀。 經(jīng)常有香客問我涯捻,道長买决,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮书幕,結果婚禮上仰美,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好齿诞,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骂租,像睡著了一般祷杈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渗饮,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天但汞,我揣著相機與錄音,去河邊找鬼互站。 笑死私蕾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的胡桃。 我是一名探鬼主播踩叭,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼标捺!你這毒婦竟也來了?” 一聲冷哼從身側響起揉抵,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤亡容,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冤今,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闺兢,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年戏罢,在試婚紗的時候發(fā)現(xiàn)自己被綠了屋谭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡龟糕,死狀恐怖桐磁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讲岁,我是刑警寧澤我擂,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站缓艳,受9級特大地震影響校摩,放射性物質發(fā)生泄漏。R本人自食惡果不足惜阶淘,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一衙吩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧溪窒,春花似錦坤塞、人聲如沸冯勉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽珠闰。三九已至,卻和暖如春瘫辩,著一層夾襖步出監(jiān)牢的瞬間伏嗜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工伐厌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留承绸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓挣轨,卻偏偏與公主長得像军熏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子卷扮,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

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

  • 世有一職業(yè):畫妖師荡澎。只要出得起價錢,什么妖怪都能畫出來晤锹。但畫妖師也是分不同等級的摩幔,等級越高,畫出的妖...
    骨頭兔子閱讀 437評論 0 2
  • 王菲的《清風徐來》一開始紅遍網(wǎng)絡的時候鞭铆,我并不是很感冒或衡,聽了一遍,沒聽完车遂,就關了 然后封断,就看到各大音樂網(wǎng)站排行榜上...
    彼岸青園閱讀 257評論 0 1
  • 英國證券交易所前主管古迪遜曾經(jīng)提出: 別做累壞的主管,管理是讓別人工作的藝術舶担。 現(xiàn)在很多培訓機構的校長主管們覺得很...
    海鷗老師閱讀 686評論 0 1