c 11新特性之右值引用

2021-10-05 17:11:49 字數 4353 閱讀 5785

c++11 增加了乙個新的型別,稱為右值引用(r-value reference),標記為 t &&。在介紹右值引用型別之前先要了解什麼是左值和右值。左值是指表示式結束後依然存在的持久物件,右值是指表示式結束時就不再存在的臨時物件。乙個區分左值與右值的便捷方法是:看能不能對表示式取位址,如果能,則為左值,否則為右值 。所有的具名變數或物件都是左值,而右值不具名。

在 c++11 中,右值由兩個概念構成,乙個是將亡值(xvalue, expiring value),另乙個則是純右值(prvalue, purervalue),比如,非引用返回的臨時變數、運算表示式產生的臨時變數、原始字面量和 lambda 表示式等都是純右值。而將亡值是 c++11 新增的、與右值引用相關的表示式,比如,將要被移動的物件、 t&& 函式返回值、 std::move 返回值和轉換為 t&&的型別的轉換函式的返回值。

c++11 中所有的值必屬於左值、將亡值、純右值三者之一 ,將亡值和純右值都屬於右值。區分表示式的左右值屬性有乙個簡便方法:若可對表示式用 & 符取址,則為左值,否則為右值。

右值引用就是對乙個右值進行引用的型別。因為右值不具名,所以我們只能通過引用的方式找到它。

無論宣告左值引用還是右值引用都必須立即進行初始化,因為引用型別本身並不擁有所繫結物件的記憶體,只是該物件的乙個別名。通過右值引用的宣告,該右值又「重獲新生」,其生命週期與右值引用型別變數的生命週期一樣,只要該變數還活著,該右值臨時量將會一直存活下去。

class a_2

~a_2()

private:

int* m_ptr;};

a_2 get(bool flag)

int right_value_ref2()

在上面的**中,預設建構函式是淺拷貝,a和b會指向同一指標m_ptr,在析構函式會導致重複刪除該指標。

正確的做法是提供深拷貝的拷貝建構函式。

a_2(const a_2& a):m_ptr(new int(*a.m_ptr)) //深拷貝

//這樣就可以保證拷貝構造時的安全性,但有時這種拷貝構造卻是不必要的,不人上面**中的拷貝構造就是不必要的。上面**中的get函式會返回臨時變數,

//然後通過這個臨時變數拷貝構造乙個新的物件b,臨時變數在拷貝構造完成之後就銷毀了,如果堆記憶體很大,那麼,這個拷貝構造的代價會很大,帶來了額外的效能損耗。

//有沒有辦法避免臨時物件的拷貝構造呢?答案是肯定的。看下面的**:

class a_2

a_2(const a_2& a):m_ptr(new int(*a.m_ptr)) //深拷貝

a_2(a_2&& a) :m_ptr(a.m_ptr)

~a_2()

private:

int* m_ptr;};

a_2 get(bool flag)

int right_value_ref2()

上面的**中沒有了拷貝構造,取而代之的是移動構造(move construct)。從移動建構函式的實現可以看到,它的引數是乙個右值引用型別的引數a&&,這裡沒有深拷貝,只有淺拷貝,這樣就避免了臨時物件的深拷貝,提供了效能。這裡的a&&用來根據引數是左值還是右值來建立分支,如果是臨時值,則會選擇移動建構函式。移動建構函式只是將臨時物件的資源做了淺拷貝,不需要對其進行深拷貝,從而避免了額外的拷貝,提高效能。這也就是所謂的移動語義(move 語義),右值引用的乙個重要目的是用來支援移動語義的。值得注意的是移動構造裡面將a.m_ptr置為nullptr(注釋寫得很清楚),此時delete不會對nullptr做出任何操作。

移動語義可以將資源(堆、系統物件等)通過淺拷貝方式從乙個物件轉移到另乙個物件,這樣能減少不必要的臨時物件的建立、拷貝以及銷毀,可以大幅度提高c++應用程式的效能,消除臨時物件的維護(建立和銷毀)對效能的影響

再看乙個簡單的例子,**如下:

struct element

// 右值版本的拷貝建構函式

element(element&& other) : m_children(std::move(other.m_children)){}

element(const element& other) : m_children(other.m_children){}

private:

vectorm_children;

};

這個 element 類提供了乙個右值版本的建構函式。這個右值版本的建構函式的乙個典型

應用場景如下:

void test()

先構造了乙個臨時物件 t1,這個物件中乙個存放了很多 element 物件,數量可能很多,如果直接將這個 t1 用 push_back 插入到 vector 中,沒有右值版本的建構函式時,會引起大量的拷貝,這種拷貝會造成額外的嚴重的效能損耗。通過定義右值版本的建構函式以及

std::move(t1) 就可以避免這種額外的拷貝,從而大幅提高效能。

有了右值引用和移動語義,在設計和實現類時,對於需要動態申請大量資源的類,應該設計右值引用的拷貝建構函式和賦值函式,以提高應用程式的效率。需要注意的是,我們一般在提供右值引用的建構函式的同時,也會提供常量左值引用的拷貝建構函式,以保證移動不成還可以使用拷貝構造。

這裡也要注意對 move 語義的誤解, move 只是轉移了資源的控制權,本質上是將左值強制轉換為右值引用,以用於 move 語義,避免含有資源的物件發生無謂的拷貝。 move 對於擁有形如對記憶體、檔案控制代碼等資源的成員的物件有效。如果是一些基本型別,比如 int 和char[10] 陣列等,如果使用 move,仍然會發生拷貝(因為沒有對應的移動建構函式),所以說move 對於含資源的物件來說更有意義。

還要注意的是,右值引用不能繫結左值:int a;  int &&c = a;   這樣是不行的。

另附一段右值引用效能測試的**:

#define  max_times 1000

class a

a(const a &a)

a &operator =(const a &a)

return *this;

} a(a &&a) : m_data(a.m_data)

a & operator = (a &&a)

return *this;

} ~a()

private:

char * m_data;

};//移動語義實現

void swap1(a& a, a& b)

// 普通交換

void swap2(a& a, a& b)

int main()

finish = clock();

totaltime = (double)(finish - start) / clocks_per_sec;

cout << "max_times =" << max_times << "\n右值引用的執行時間為" << totaltime << "秒!" << endl;

start1 = clock();

for (int i = 0; i < max_times; ++i)

finish1 = clock();

totaltime = (double)(finish1 - start1) / clocks_per_sec;

cout << "max_times =" << max_times << "\n普通的執行時間為" << totaltime << "秒!" << endl;

cin.get();

return 0;

}

測試結果:

10000次以下時幾乎沒有差別,這裡不列出;

通過大量的測試表明,右值引用內比普通的構造效能確實要高,我這邊測試大約是15倍的樣子。

C 11新特性之右值引用

右值 只能出現在operator 右邊的 通常臨時物件 將亡值 字面值常量 純右值 是右值 類的臨時物件是乙個右值,臨時變數一定被當成右值,因為臨時物件建立之後不會再被使用,所以直接把右值資料引用給別的變數,有時候乙個左值在後面不會被用到,那麼就可以使用move語義把左值轉成右值。右值引用和左值引用...

C 11新特性之右值引用

int i 10 右值引用 右值 int rr 42 int rr1 std move i const左值引用 右值 const int c 42 左值引用 左值 int lr rr 注意 右值引用也是變數,所以rr是左值。string a aaa string r std move a r rrr...

C 11之 右值引用

最近在看cocos2dx的源 發現了乙個模板類有乙個奇怪的語法 inline refptr refptr other 剛開始一陣犯暈,乙個型別ref和乙個似乎是形參的other與操作會得出乙個什麼?顯然這種理解是錯誤的。還有一種含義是右值引用,表示形參是乙個右值。左值 右值 int a 0 a是乙個...